Merge branch 'main' into fix/added-False-in-save

pull/11642/head
Jaivignesh J 2024-02-14 07:01:09 +05:30 zatwierdzone przez GitHub
commit 6e5450df6e
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
36 zmienionych plików z 490 dodań i 170 usunięć

Wyświetl plik

@ -9,12 +9,15 @@ Changelog
* Reduce memory usage when rebuilding search indexes (Jake Howard)
* Support creating images in .ico format (Jake Howard)
* Add the ability to disable the usage of a shared password for enhanced security for the private pages and collections (documents) feature (Salvo Polizzi, Jake Howard)
* Add system checks to ensure that `WAGTAIL_DATE_FORMAT`, `WAGTAIL_DATETIME_FORMAT`, `WAGTAIL_TIME_FORMAT` are correctly configured (Rohit Sharma, Coen van der Kamp)
* Fix: Fix typo in `__str__` for MySQL search index (Jake Howard)
* Fix: Ensure that unit tests correctly check for migrations in all core Wagtail apps (Matt Westcott)
* Fix: Correctly handle `date` objects on `human_readable_date` template tag (Jhonatan Lopes)
* Fix: Ensure re-ordering buttons work correctly when using a nested InlinePanel (Adrien Hamraoui)
* Fix: Resolve inconsistent use of model `verbose_name` in group edit view when listing custom permissions (Neeraj Yetheendran, Omkar Jadhav)
* Docs: Add contributing development documentation on how to work with a fork of Wagtail (Nix Asteri, Dan Braghis)
* Docs: Make sure the settings panel is listed in tabbed interface examples (Tibor Leupold)
* Docs: Update multiple pages in the reference sections and multiple page names to their US spelling instead of UK spelling (Victoria Poromon)
* Docs: Update content and page names to their US spelling instead of UK spelling (Victoria Poromon)
* Maintenance: Move RichText HTML whitelist parser to use the faster, built in `html.parser` (Jake Howard)
* Maintenance: Remove duplicate 'path' in default_exclude_fields_in_copy (Ramchandra Shahi Thakuri)
* Maintenance: Update unit tests to always use the faster, built in `html.parser` & remove `html5lib` dependency (Jake Howard)
@ -22,6 +25,7 @@ Changelog
* Maintenance: Rename the React `Button` that only renders links (a element) to `Link` and remove unused prop & behavior that was non-compliant for aria role usage (Advik Kabra)
* Maintenance: Set up an `wagtail.models.AbstractWorkflow` model to support future customisations around workflows (Hossein)
* Maintenance: Improve `classnames` template tag to handle nested lists of strings, use template tag for admin `body` element (LB (Ben) Johnston)
* Maintenance: Merge `UploadedDocument` and `UploadedImage` into new `UploadedFile` model for easier shared code usage (Advik Kabra, Karl Hobley)
6.0 (07.02.2024)

Wyświetl plik

@ -798,6 +798,7 @@
* Hossein
* Andre Delorme
* arshyia3000
* Adrien Hamraoui
## Translators

Wyświetl plik

@ -52,8 +52,10 @@ export class InlinePanel extends ExpandingFormset {
const childId = 'inline_child_' + prefix;
const deleteInputId = 'id_' + prefix + '-DELETE';
const currentChild = $('#' + childId);
const $up = currentChild.find('[data-inline-panel-child-move-up]');
const $down = currentChild.find('[data-inline-panel-child-move-down]');
const $up = currentChild.find('[data-inline-panel-child-move-up]:first ');
const $down = currentChild.find(
'[data-inline-panel-child-move-down]:first ',
);
$('#' + deleteInputId + '-button').on('click', () => {
/* set 'deleted' form field to true */
@ -148,8 +150,14 @@ export class InlinePanel extends ExpandingFormset {
forms.each(function updateButtonStates(i) {
const isFirst = i === 0;
const isLast = i === forms.length - 1;
$('[data-inline-panel-child-move-up]', this).prop('disabled', isFirst);
$('[data-inline-panel-child-move-down]', this).prop('disabled', isLast);
$('[data-inline-panel-child-move-up]:first', this).prop(
'disabled',
isFirst,
);
$('[data-inline-panel-child-move-down]:first', this).prop(
'disabled',
isLast,
);
});
}
}

Wyświetl plik

@ -74,8 +74,8 @@ To easily identify the most relevant changes to users, items are grouped togethe
- Major features (no prefix) - things that will inspire users to upgrade to a new release
- Minor enhancements (no prefix) - other improvements to the developer or end user experience
- Bug fixes (prefixed with "Fix:") - things that address broken behaviour from previous releases
- Documentation (prefixed with "Docs:") - changes to documentation that do not accompany a specific code change; reorganisations, tutorials, recipes and so on
- Bug fixes (prefixed with "Fix:") - things that address broken behavior from previous releases
- Documentation (prefixed with "Docs:") - changes to documentation that do not accompany a specific code change; reorganizations, tutorials, recipes and so on
- Maintenance (prefixed with "Maintenance:") - cleanup, refactoring and other changes to code or tooling that are not intended to have a visible effect to developers or end users
The name of the contributor should be added at the end of the summary, in brackets.
@ -131,7 +131,7 @@ git branch -d pr/xxxx
## When you have made a mistake
It's ok! Everyone makes mistakes. If you realise that recently merged changes
It's ok! Everyone makes mistakes. If you realize that recently merged changes
have a negative impact, create a new pull request with a revert of the changes
and merge it without waiting for a review. The PR will serve as additional
documentation for the changes and will run through the CI tests.

Wyświetl plik

@ -237,7 +237,7 @@ We want to make Wagtail accessible for users of a wide variety of assistive tech
- Mobile [VoiceOver](https://support.apple.com/en-gb/guide/voiceover-guide/welcome/web) on iOS, or [TalkBack](https://support.google.com/accessibility/android/answer/6283677?hl=en-GB) on Android
- Windows [High-contrast mode](https://support.microsoft.com/en-us/windows/use-high-contrast-mode-in-windows-10-fedc744c-90ac-69df-aed5-c8a90125e696)
We aim for Wagtail to work in those environments. Our development standards ensure that the site is usable with other assistive technologies. In practice, testing with assistive technology can be a daunting task that requires specialised training – here are tools we rely on to help identify accessibility issues, to use during development and code reviews:
We aim for Wagtail to work in those environments. Our development standards ensure that the site is usable with other assistive technologies. In practice, testing with assistive technology can be a daunting task that requires specialized training – here are tools we rely on to help identify accessibility issues, to use during development and code reviews:
- [@wordpress/jest-puppeteer-axe](https://github.com/WordPress/gutenberg/tree/trunk/packages/jest-puppeteer-axe) running Axe checks as part of integration tests.
- [Axe](https://chrome.google.com/webstore/detail/axe/lhdoppojpmngadmnindnejefpokejbdd) Chrome extension for more comprehensive automated tests of a given page.

Wyświetl plik

@ -422,11 +422,11 @@ Avoid documentation source comments in committed documentation.
### Figure
reStructuredText figures (`.. figure::`) only offer very marginal improvements over vanilla images. If your figure has a caption, add it as an italicised paragraph underneath the image.
reStructuredText figures (`.. figure::`) only offer very marginal improvements over vanilla images. If your figure has a caption, add it as an italicized paragraph underneath the image.
### Other reStructuredText syntax and Sphinx directives
We generally want to favour Markdown over reStructuredText, to make it as simple as possible for newcomers to make documentation contributions to Wagtail. Always prefer Markdown, unless the documents formatting highly depends on reStructuredText syntax.
We generally want to favor Markdown over reStructuredText, to make it as simple as possible for newcomers to make documentation contributions to Wagtail. Always prefer Markdown, unless the documents formatting highly depends on reStructuredText syntax.
If you want to use a specific Sphinx directive, consult with core contributors to see whether its usage is justified, and document its expected usage on this page.

Wyświetl plik

@ -66,7 +66,7 @@ You may also want to join StackOverflow and [follow the Wagtail tag](https://sta
### 3. Before contributing code
Firstly, it is important to be able to understand how to **build with** Wagtail before you can understand how to contribute **to** Wagtail. Take the time to do the full [Wagtail getting started tutorial](../getting_started/index) without focusing yet on how to contribute code but instead how to use Wagtail to build your own basic demo website. This will require you to have Python and other dependencies installed on your machine and may not be easy the first time, but keep at it and ask questions if you get stuck.
Firstly, it is important to be able to understand how to **build with** Wagtail before you can understand how to contribute **to** Wagtail. Take the time to do the full [Wagtail getting started tutorial](../getting_started/index) without focusing yet on how to contribute code but instead on how to use Wagtail to build your own basic demo website. This will require you to have Python and other dependencies installed on your machine and may not be easy the first time, but keep at it and ask questions if you get stuck.
Remember that there are many other ways to contribute, such as answering questions in StackOverflow or `#support`, contributing to one of the [other packages](https://github.com/wagtail/) or even the [Wagtail user guide](https://guide.wagtail.org/en-latest/contributing/). Sometimes, it's best to get started with a non-code contribution to get a feel for Wagtail's code or the CMS interface.
@ -80,7 +80,7 @@ Take the time to **read** the issue and links before adding new comments or ques
```
- [ ] Do the Wagtail tutorial
- [ ] Look at the Wagtail organisation on GitHub, take note of any interesting projects
- [ ] Look at the Wagtail organization on GitHub, take note of any interesting projects
- [ ] Read through the Issue Tracking section in the docs
- [ ] Give a go at a non-code contribution
```
@ -268,7 +268,7 @@ You can add a comment if you want to the pull request that you have updated, but
### 8. Next steps
When you take time to contribute out of your own personal time, or even that from your paid employer, it can be very frustrating when a pull request does not get reviewed. It is best to temper your expectations with this process and remember that many people on the other side of this are also volunteers or have limited time to prioritise.
When you take time to contribute out of your own personal time, or even that from your paid employer, it can be very frustrating when a pull request does not get reviewed. It is best to temper your expectations with this process and remember that many people on the other side of this are also volunteers or have limited time to prioritize.
It is best to celebrate your accomplishment at this point even if your pull request never gets merged. It's good to balance that with an eagerness about getting your amazing fix in place to help people who use the project. Balancing this tension is hard, but the unhelpful thing to do is give up and never contribute or decide that you wont respond to feedback because it came too late.
@ -321,7 +321,7 @@ Unless you are updating the documentation or only making visual style changes, y
If you are new to writing tests in Django, start by reading the [Django documentation on testing](inv:django#topics/testing/overview). Re-read the [Wagtail documentation notes on testing](testing) and have a look at [existing tests](https://cs.github.com/?scopeName=All+repos&scope=&q=repo%3Awagtail%2Fwagtail+path%3A**%2Ftests%2F**).
Note that the JavaScript testing is not as robust as the Python testing, if possible at least attempt to add some basic JS tests to new behaviour.
Note that the JavaScript testing is not as robust as the Python testing, if possible at least attempt to add some basic JS tests to new behavior.
### Where can I get help with my pull request (PR)?
@ -344,7 +344,7 @@ Here are some links for using Gitpod with the Wagtail packages:
### How can I be assigned an issue to contribute to
We only use GitHub's issue assignment feature to members of the Wagtail core team for when tasks are being planned as part of core roadmap features or when being used for a specific internship program. If an issue is not assigned to anyone, feel free to work on it, there is no need to ask to be assigned the issue.
We only use GitHub's issue assignment feature for members of the Wagtail core team when tasks are being planned as part of core roadmap features or when being used for a specific internship program. If an issue is not assigned to anyone, feel free to work on it, there is no need to ask to be assigned the issue.
Instead, review the issue, understand it and if you feel you can contribute you can just raise a Pull Request, or add a comment that you are taking a look at this. There are no strict claiming or reserving rules in place, anyone is free to work on any issue, but try to avoid double effort if someone has already got a Pull Request underway.

Wyświetl plik

@ -10,7 +10,7 @@ Thank you for your interest in improving Wagtail!
## Issues
The easiest way to contribute to Wagtail is to tell us how to improve it! First, check to see if your bug or feature request has already been submitted at [github.com/wagtail/wagtail/issues](https://github.com/wagtail/wagtail/issues). If it has, and you have some supporting information which may help us deal with it, comment on the existing issue. If not, please [create a new one](https://github.com/wagtail/wagtail/issues/new), providing as much relevant context as possible. For example, if you're experiencing problems with installation, detail your environment and the steps you've already taken. If something isn't displaying correctly, tell us what browser you're using, and include a screenshot if possible.
The easiest way to contribute to Wagtail is to tell us how to improve it! First, check to see if your bug or feature request has already been submitted at [github.com/wagtail/wagtail/issues](https://github.com/wagtail/wagtail/issues). If it has, and you have some supporting information that may help us deal with it, comment on the existing issue. If not, please [create a new one](https://github.com/wagtail/wagtail/issues/new), providing as much relevant context as possible. For example, if you're experiencing problems with installation, detail your environment and the steps you've already taken. If something isn't displaying correctly, tell us what browser you're using, and include a screenshot if possible.
If your bug report is a security issue, **do not** report it with an issue. Please read our guide to [reporting security issues](security).
@ -24,7 +24,7 @@ issue_tracking
If you are just getting started with development and have never contributed to an open-source project, we recommend you read the [Your first contribution guide](first_contribution_guide). If you're a confident Python or Django developer, [fork it](https://github.com/wagtail/wagtail/) and read the [developing docs](developing_for_wagtail) to get stuck in!
We welcome all contributions, whether they solve problems which are specific to you or they address existing issues. If you're stuck for ideas, pick something from the [issue list](https://github.com/wagtail/wagtail/issues?q=is%3Aopen), or email us directly on [hello@wagtail.org](mailto:hello@wagtail.org) if you'd like us to suggest something!
We welcome all contributions, whether they solve problems that are specific to you or they address existing issues. If you're stuck for ideas, pick something from the [issue list](https://github.com/wagtail/wagtail/issues?q=is%3Aopen), or email us directly at [hello@wagtail.org](mailto:hello@wagtail.org) if you'd like us to suggest something!
For large-scale changes, we'd generally recommend breaking them down into smaller pull requests that achieve a single well-defined task and can be reviewed individually. If this isn't possible, we recommend opening a pull request on the [Wagtail RFCs](https://github.com/wagtail/rfcs/) repository, so that there's a chance for the community to discuss the change before it gets implemented.
@ -37,7 +37,7 @@ developing
## Translations
Wagtail has internationalisation support so if you are fluent in a non-English language you can contribute by localising the interface.
Wagtail has internationalization support so if you are fluent in a non-English language you can contribute by localizing the interface.
Translation work should be submitted through [Transifex](https://explore.transifex.com/torchbox/wagtail/), for information on how to get started see [](contributing_translations).
@ -48,8 +48,8 @@ Translation work should be submitted through [Transifex](https://explore.transif
We welcome contributions to all aspects of Wagtail. If you would like to improve the design of the user interface, or extend the documentation, please submit a pull request as above. Here are some other ways to contribute if you are getting started or have been using Wagtail for a long time but are unable to contribute code.
- Contribute to one of the other [core Wagtail projects](https://github.com/orgs/wagtail/repositories) in GitHub.
- Contribute to one of the community maintained packages on [Wagtail Nest](https://github.com/wagtail-nest/).
- Contribute user facing documentation (including translations) on the [Wagtail guide](https://guide.wagtail.org/en-latest/contributing/).
- Contribute to one of the community-maintained packages on [Wagtail Nest](https://github.com/wagtail-nest/).
- Contribute user-facing documentation (including translations) on the [Wagtail guide](https://guide.wagtail.org/en-latest/contributing/).
### Non-code contributions

Wyświetl plik

@ -10,12 +10,12 @@ Do not use issues for support queries or other questions ("How do I do X?" - alt
As soon as a ticket is opened - ideally within one day - a member of the core team will give it an initial classification, by either closing it due to it being invalid or updating it with the relevant labels. When a bug is opened, it will automatically be assigned the [`type:Bug`](https://github.com/wagtail/wagtail/labels/type%3ABug) and [`status:Unconfirmed`](https://github.com/wagtail/wagtail/labels/status%3AUnconfirmed) labels, once confirmed the bug can have the unconfirmed status removed. A member of the team will potentially also add a release milestone to help guide the priority of this issue. Anyone is invited to help Wagtail with reproducing `status:Unconfirmed` bugs and commenting if it is a valid bug or not with additional steps to reproduce if needed.
Don't be discouraged if you feel that your ticket has been given a lower priority than it deserves - this decision isn't permanent. We will consider all feedback, and reassign or reopen tickets where appropriate. (From the other side, this means that the core team member doing the classification should feel free to make bold unilateral decisions - there's no need to seek consensus first. If they make the wrong judgement call, that can always be reversed later.)
Don't be discouraged if you feel that your ticket has been given a lower priority than it deserves - this decision isn't permanent. We will consider all feedback, and reassign or reopen tickets where appropriate. (From the other side, this means that the core team member doing the classification should feel free to make bold unilateral decisions - there's no need to seek consensus first. If they make the wrong judgment call, that can always be reversed later.)
The possible milestones that it might be assigned to are as follows:
- **invalid** (closed): this issue doesn't identify a specific action to be taken, or the action is not one that we want to take. For example - a bug report for something that's working as designed, or a feature request for something that's actively harmful.
- **real-soon-now**: no-one on the core team has resources allocated to work on this right now, but we know it's a pain point, and it will be prioritised whenever we next get a chance to choose something new to work on. In practice, that kind of free choice doesn't happen very often - there are lots of pressures determining what we work on from day to day - so if this is a feature or fix you need, we encourage you to work on it and contribute a pull request, rather than waiting for the core team to get round to it!
- **real-soon-now**: no-one on the core team has resources allocated to work on this right now, but we know it's a pain point, and it will be prioritized whenever we next get a chance to choose something new to work on. In practice, that kind of free choice doesn't happen very often - there are lots of pressures determining what we work on from day to day - so if this is a feature or fix you need, we encourage you to work on it and contribute a pull request, rather than waiting for the core team to get round to it!
- A specific version number (for example **1.6**): the issue is important enough that it needs to be fixed in this version. There are resources allocated and/or plans to work on the issue in the given version.
- No milestone: the issue is accepted as valid once the `status:Unconfirmed` label is removed (when it's confirmed as a report for a legitimate bug, or a useful feature request) but is not deemed a priority to work on (in the opinion of the core team). For example - a bug that's only cosmetic, or a feature that would be kind of neat but not really essential. There are no resources allocated to it - feel free to take it on!
@ -24,7 +24,7 @@ On some occasions it may take longer for the core team to classify an issue into
- It may require a non-trivial amount of work to confirm the presence of a bug. In this case, feedback and further details from other contributors, whether or not they can replicate the bug, would be particularly welcomed.
- It may require further discussion to decide whether the proposal is a good idea or not - if so, it will be tagged ["design decision needed"](https://github.com/wagtail/wagtail/labels/status%3ANeeds%20Design%20Decision).
We will endeavour to make sure that issues don't remain in this state for prolonged periods. Issues and PRs tagged "design decision needed" will be revisited regularly and discussed with at least two core contributors - we aim to review each ticket at least once per release cycle (= 6 weeks) as part of weekly core team meetings.
We will endeavor to make sure that issues don't remain in this state for prolonged periods. Issues and PRs tagged "design decision needed" will be revisited regularly and discussed with at least two core contributors - we aim to review each ticket at least once per release cycle (= 6 weeks) as part of weekly core team meetings.
## Pull requests
@ -34,7 +34,7 @@ As with issues, the core team will classify pull requests as soon as they are op
- Core team members are invited to assign themselves to the pull request for review.
- More specific details on how to triage Pull Requests can be found on the [PR triage wiki page](https://github.com/wagtail/wagtail/wiki/PR-triage).
Subsequently (ideally within a week or two, but possibly longer for larger submissions) a core team member will merge it if it is ready to be merged, or tag it as requiring further work ('needs work' / 'needs tests' / 'needs docs'). Pull requests that require further work are handled and prioritised in the same way as issues - anyone is welcome to pick one up from the backlog, whether or not they were the original committer.
Subsequently (ideally within a week or two, but possibly longer for larger submissions) a core team member will merge it if it is ready to be merged, or tag it as requiring further work ('needs work' / 'needs tests' / 'needs docs'). Pull requests that require further work are handled and prioritized in the same way as issues - anyone is welcome to pick one up from the backlog, whether or not they were the original committer.
Rebasing / squashing of pull requests is welcome, but not essential. When doing so, do not squash commits that need reviewing into previous ones and make sure to preserve the sequence of changes. To fix mistakes in earlier commits, use `git commit --fixup` so that the final merge can be done with `git rebase -i --autosquash`.

Wyświetl plik

@ -33,7 +33,7 @@ else:
Always compare against the version using greater-or-equals (`>=`), so that code for newer versions of Django is first.
Do not use a `try ... except` when seeing if an object has an attribute or method introduced in a newer versions of Django, as it does not clearly express why the `try ... except` is used. An explicit check against the Django version makes the intention of the code very clear.
Do not use a `try ... except` when seeing if an object has an attribute or method introduced in newer versions of Django, as it does not clearly express why the `try ... except` is used. An explicit check against the Django version makes the intention of the code very clear.
```python
# Do not do this

Wyświetl plik

@ -13,7 +13,7 @@ Most normal bugs in Wagtail are reported as [GitHub issues](https://github.com/w
Instead, if you believe you've found something in Wagtail which has security implications, please send a description of the issue via email to <security@wagtail.org>.
Mail sent to that address reaches a subset of the core team, who can forward security issues to other core team members for broader discussion if needed.
Once you've submitted an issue via email, you should receive an acknowledgement from a member of the security team within 48 hours, and depending on the action to be taken, you may receive further followup emails.
Once you've submitted an issue via email, you should receive an acknowledgment from a member of the security team within 48 hours, and depending on the action to be taken, you may receive further followup emails.
If you want to send an encrypted email (optional), the public key ID for <security@wagtail.org> is `0xbed227b4daf93ff9`, and this public key is available from most commonly-used keyservers.
@ -57,4 +57,4 @@ In various places Wagtail provides the option to export data in CSV format, and
Since the CSV format has no concept of formulae or macros, there is also no agreed-upon convention for escaping data to prevent it from being interpreted in that way; commonly-suggested approaches such as prefixing the field with a quote character would corrupt legitimate data (such as phone numbers beginning with '+') when interpreted by software correctly following the CSV specification.
Wagtail's data exports default to XLSX, which can be loaded into spreadsheet software without any such issues. This minimises the risk of a user handling CSV files insecurely, as they would have to explicitly choose CSV over the more familiar XLSX format.
Wagtail's data exports default to XLSX, which can be loaded into spreadsheet software without any such issues. This minimizes the risk of a user handling CSV files insecurely, as they would have to explicitly choose CSV over the more familiar XLSX format.

Wyświetl plik

@ -5,12 +5,12 @@
Wagtail uses [Transifex](https://www.transifex.com/) to translate the content for the admin interface. Our goal is to ensure that Wagtail can be used by those who speak many different languages. Translation of admin content is a great way to contribute without needing to know how to write code.
```{note}
For translations and internationalisation of content made with Wagtail see [](internationalisation).
For translations and internationalization of content made with Wagtail see [](internationalisation).
```
## Translation workflow
Wagtail is localised (translated) using Django's [translation system](inv:django#topics/i18n/translation) and the translations are provided to and managed by [Transifex](https://www.transifex.com/), a web platform that helps organisations coordinate translation projects.
Wagtail is localized (translated) using Django's [translation system](inv:django#topics/i18n/translation) and the translations are provided to and managed by [Transifex](https://www.transifex.com/), a web platform that helps organizations coordinate translation projects.
Translations from Transifex are only integrated into the repository at the time of a new release. When a release is close to being ready there will be a RC (Release Candidate) for the upcoming version and the translations will be exported to Transifex.
@ -26,7 +26,7 @@ These new translations are imported into Wagtail for any subsequent RC and the f
- Click on start for free
- Fill in your Username, Email and Password
- Agree to the terms and conditions
- Click on free trial or join an existing organisation
- Click on free trial or join an existing organization
- Join [Wagtail](https://app.transifex.com/torchbox/wagtail/dashboard/) and see the list of languages on the dashboard
- Request access to become a member of the language team you want to work with on Slack (mention your Transifex username)
- A view resources button appears when you hover over the ready to use part on the right side of the page
@ -40,7 +40,7 @@ These new translations are imported into Wagtail for any subsequent RC and the f
In code, strings can be marked for translation with using Django's [translation system](inv:django#topics/i18n/translation), using `gettext` or `gettext_lazy` in Python and `blocktranslate`, `translate`, and `_(" ")` in templates.
In both Python and templates, make sure to always use named placeholder. In addition, in Python, only use the printf style formatting. This is to ensure compatibility with Transifex and help translators in their work.
In both Python and templates, make sure to always use a named placeholder. In addition, in Python, only use the printf style formatting. This is to ensure compatibility with Transifex and help translators in their work.
### Translations within Python

Wyświetl plik

@ -22,7 +22,7 @@ We use [djhtml](https://github.com/rtts/djhtml) for formatting and [Curlylint](h
- Write [valid](https://validator.w3.org/nu/), [semantic](https://html5doctor.com/element-index/) HTML.
- Follow [ARIA authoring practices](https://w3c.github.io/aria-practices/), in particular, [No ARIA is better than Bad ARIA](https://w3c.github.io/aria-practices/#no_aria_better_bad_aria).
- Use IDs for semantics only, classes for styling, `data-` attributes for JavaScript behaviour.
- Use IDs for semantics only, classes for styling, `data-` attributes for JavaScript behavior.
- Order attributes with `id` first, then `class`, then any `data-` or other attributes with Stimulus `data-controller` first.
- For comments, use [Django template syntax](https://docs.djangoproject.com/en/stable/ref/templates/language/#comments) instead of HTML.
@ -33,7 +33,7 @@ We use [Prettier](https://prettier.io/) for formatting and [Stylelint](https://s
- We follow [BEM](https://getbem.com/) and [ITCSS](https://www.xfive.co/blog/itcss-scalable-maintainable-css-architecture/), with a large amount of utilities created with [Tailwind](https://tailwindcss.com/).
- Familiarise yourself with our [stylelint-config-wagtail](https://github.com/wagtail/stylelint-config-wagtail) configuration, which details our preferred code style.
- Use `rems` for `font-size`, because they offer absolute control over text. Additionally, unit-less `line-height` is preferred because it does not inherit a percentage value of its parent element, but instead is based on a multiplier of the `font-size`.
- Always use variables for design tokens such as colours or font sizes, rather than hard-coding specific values.
- Always use variables for design tokens such as colors or font sizes, rather than hard-coding specific values.
- We use the `w-` prefix for all styles intended to be reusable by Wagtail site implementers.
### Stylesheets
@ -100,7 +100,7 @@ We use [Prettier](https://prettier.io/) for formatting and [ESLint](https://esli
## Stimulus
Wagtail uses [Stimulus](https://stimulus.hotwired.dev/) as a lightweight framework to attach interactive behaviour to DOM elements via `data-` attributes.
Wagtail uses [Stimulus](https://stimulus.hotwired.dev/) as a lightweight framework to attach interactive behavior to DOM elements via `data-` attributes.
### Why Stimulus
@ -122,17 +122,17 @@ First think of how to name the controller. Keep it concise, one or two words ide
1. Start with the HTML templates, build as much of the UI as you can in HTML alone. Ensure it is accessible and follows the CSS guidelines.
2. Create the controller file in our `client/src/controllers` folder, along with its tests (see [](testing)) and Storybook stories.
3. For initialisation, consider which [controller lifecycle methods](https://stimulus.hotwired.dev/reference/lifecycle-callbacks#methods) to use, if any (`connect`, `initialize`).
3. For initialization, consider which [controller lifecycle methods](https://stimulus.hotwired.dev/reference/lifecycle-callbacks#methods) to use, if any (`connect`, `initialize`).
4. If relevant, also consider how to handle the controlled element being removed from the DOM [`disconnect` lifecycle method](https://stimulus.hotwired.dev/reference/lifecycle-callbacks#disconnection).
5. Document controller classes and methods with [JSDoc annotations](https://jsdoc.app/index.html).
6. Use [values](https://stimulus.hotwired.dev/reference/values) to provide options and also reactive state, avoiding instance properties if possible. Prefer falsey or empty defaults and avoid too much usage of the Object type when using values.
7. Build the behaviour around small, discrete, methods and use [Stimulus actions](https://stimulus.hotwired.dev/reference/actions) declared in HTML to drive when they are called.
7. Build the behavior around small, discrete, methods and use [Stimulus actions](https://stimulus.hotwired.dev/reference/actions) declared in HTML to drive when they are called.
### Helpful tips
- Prefer controllers that do a small amount of 'work' that is collected together, instead of lots of large or specific controllers.
- Lean towards dispatching events for key behaviour in the UI interaction as this provides a great way for custom code to hook into this without an explicit API, but be sure to document these.
- Multiple controllers can be attached to one DOM element for composing behaviour, where practical split out behaviour to separate controllers.
- Lean towards dispatching events for key behavior in the UI interaction as this provides a great way for custom code to hook into this without an explicit API, but be sure to document these.
- Multiple controllers can be attached to one DOM element for composing behavior, where practical split out behavior to separate controllers.
- Consider when to document controller usage for non-contributors.
- When writing unit tests, note that DOM updates triggered by data attribute changes are completed async (next `microtick`) so will require a await Promise or similar to check for the changes in JSDom.
- Avoid hard-coding a controller's identifier, instead reference it with `this.identifier` if adjusting attributes. This way the controller can be used easily with a changed identifier or extended by other classes in the future.

Wyświetl plik

@ -205,6 +205,8 @@ WAGTAILADMIN_EXTERNAL_LINK_CONVERSION = 'exact'
Customize Wagtail's behavior when an internal page url is entered in the external link chooser. Possible values for this setting are `'all'`, `'exact'`, `'confirm`, or `''`. The default, `'all'`, means that Wagtail will automatically convert submitted urls that exactly match page urls to the corresponding internal links. If the url is an inexact match - for example, the submitted url has query parameters - then Wagtail will confirm the conversion with the user. `'exact'` means that any inexact matches will be left as external urls, and the confirmation step will be skipped. `'confirm'` means that every link conversion will be confirmed with the user, even if the match is exact. `''` means that Wagtail will not attempt to convert any urls entered to internal page links.
(wagtail_date_time_formats)=
### `WAGTAIL_DATE_FORMAT`, `WAGTAIL_DATETIME_FORMAT`, `WAGTAIL_TIME_FORMAT`
```python

Wyświetl plik

@ -18,6 +18,7 @@ depth: 1
* Reduce memory usage when rebuilding search indexes (Jake Howard)
* Support creating images in .ico format (Jake Howard)
* Add the ability to disable the usage of a shared password for enhanced security for the [private pages](private_pages) and [collections (documents)](private_collections) feature (Salvo Polizzi, Jake Howard)
* Add system checks to ensure that `WAGTAIL_DATE_FORMAT`, `WAGTAIL_DATETIME_FORMAT`, `WAGTAIL_TIME_FORMAT` are [correctly configured](wagtail_date_time_formats) (Rohit Sharma, Coen van der Kamp)
### Bug fixes
@ -25,13 +26,15 @@ depth: 1
* Fix typo in `__str__` for MySQL search index (Jake Howard)
* Ensure that unit tests correctly check for migrations in all core Wagtail apps (Matt Westcott)
* Correctly handle `date` objects on `human_readable_date` template tag (Jhonatan Lopes)
* Ensure re-ordering buttons work correctly when using a nested InlinePanel (Adrien Hamraoui)
* Resolve inconsistent use of model `verbose_name` in group edit view when listing custom permissions (Neeraj Yetheendran, Omkar Jadhav)
### Documentation
* Add contributing development documentation on how to work with a fork of Wagtail (Nix Asteri, Dan Braghis)
* Make sure the settings panel is listed in tabbed interface examples (Tibor Leupold)
* Update multiple pages in the reference sections and multiple page names to their US spelling instead of UK spelling (Victoria Poromon)
* Update content and page names to their US spelling instead of UK spelling (Victoria Poromon)
### Maintenance
@ -43,6 +46,7 @@ depth: 1
* Rename the React `Button` that only renders links (a element) to `Link` and remove unused prop & behavior that was non-compliant for aria role usage (Advik Kabra)
* Set up an `wagtail.models.AbstractWorkflow` model to support future customisations around workflows (Hossein)
* Improve `classnames` template tag to handle nested lists of strings, use template tag for admin `body` element (LB (Ben) Johnston)
* Merge `UploadedDocument` and `UploadedImage` into new `UploadedFile` model for easier shared code usage (Advik Kabra, Karl Hobley)
## Upgrade considerations

Wyświetl plik

@ -235,3 +235,54 @@ def file_overwrite_check(app_configs, **kwargs):
)
return errors
@register("datetime_format")
def datetime_format_check(app_configs, **kwargs):
"""
If L10N is enabled, check if WAGTAIL_* formats are compatible with Django input formats.
See https://docs.djangoproject.com/en/stable/topics/i18n/formatting/#creating-custom-format-files
See https://docs.wagtail.org/en/stable/reference/settings.html#wagtail-date-format-wagtail-datetime-format-wagtail-time-format
"""
from django.conf import settings
from django.utils import formats, translation
errors = []
if not getattr(settings, "USE_L10N", False):
return errors
formats.FORMAT_SETTINGS = formats.FORMAT_SETTINGS.union(
[
"WAGTAIL_DATE_FORMAT",
"WAGTAIL_DATETIME_FORMAT",
"WAGTAIL_TIME_FORMAT",
]
)
for code, label in settings.LANGUAGES:
with translation.override(code):
for wagtail_format, django_formats in [
("WAGTAIL_DATE_FORMAT", "DATE_INPUT_FORMATS"),
("WAGTAIL_DATETIME_FORMAT", "DATETIME_INPUT_FORMATS"),
("WAGTAIL_TIME_FORMAT", "TIME_INPUT_FORMATS"),
]:
wagtail_format_value = getattr(settings, wagtail_format, None)
django_formats_value = getattr(settings, django_formats, None)
if wagtail_format_value is None:
# Skip the iteration if wagtail_format is not present
continue
input_format = formats.get_format_lazy(wagtail_format_value)
input_formats = formats.get_format_lazy(django_formats_value)
if str(input_format) not in str(input_formats):
errors.append(
Error(
"Configuration error",
hint=f"{wagtail_format} {input_format} must be in {django_formats} for language {label} ({code}).",
)
)
return errors

Wyświetl plik

@ -0,0 +1,92 @@
from django.core.checks import Error
from django.test import TestCase, override_settings
from wagtail.admin.checks import datetime_format_check
from wagtail.test.utils import WagtailTestUtils
class TestDateTimeChecks(WagtailTestUtils, TestCase):
fixtures = ["test.json"]
def test_datetime_format(self):
with override_settings(
WAGTAIL_CONTENT_LANGUAGES=[
("en", "English"),
],
LANGUAGES=[
("en", "English"),
],
WAGTAIL_DATE_FORMAT="%m/%d/%Y",
WAGTAIL_TIME_FORMAT="%H:%M",
USE_L10N=True,
):
errors = datetime_format_check(None)
self.assertEqual(errors, [])
def test_datetime_format_with_unsupported_date(self):
with override_settings(
WAGTAIL_CONTENT_LANGUAGES=[
("en", "English"),
],
LANGUAGES=[
("en", "English"),
],
WAGTAIL_DATE_FORMAT="%d.%m.%Y.",
WAGTAIL_TIME_FORMAT="%H:%M",
USE_L10N=True,
):
errors = datetime_format_check(None)
expected_errors = [
Error(
"Configuration error",
hint="WAGTAIL_DATE_FORMAT %d.%m.%Y. must be in DATE_INPUT_FORMATS for language English (en).",
)
]
self.assertEqual(errors, expected_errors)
def test_datetime_format_with_unsupported_date_not_using_l10n(self):
"""
Test that the check doesn't raise an error when USE_L10N is False.
"""
with override_settings(
WAGTAIL_CONTENT_LANGUAGES=[
("en", "English"),
],
LANGUAGES=[
("en", "English"),
],
WAGTAIL_DATE_FORMAT="%d.%m.%Y.",
WAGTAIL_TIME_FORMAT="%H:%M",
USE_L10N=False,
):
errors = datetime_format_check(None)
self.assertEqual(errors, [])
def test_datetime_format_with_unsupported_datetime_and_time(self):
with override_settings(
WAGTAIL_CONTENT_LANGUAGES=[
("en", "English"),
],
LANGUAGES=[
("en", "English"),
],
WAGTAIL_DATETIME_FORMAT="%d.%m.%Y. %H:%M",
WAGTAIL_TIME_FORMAT="%I:%M %p",
USE_L10N=True,
):
errors = datetime_format_check(None)
expected_errors = [
Error(
"Configuration error",
hint="WAGTAIL_DATETIME_FORMAT %d.%m.%Y. %H:%M must be in DATETIME_INPUT_FORMATS for language English (en).",
),
Error(
"Configuration error",
hint="WAGTAIL_TIME_FORMAT %I:%M %p must be in TIME_INPUT_FORMATS for language English (en).",
),
]
self.assertEqual(errors, expected_errors)

Wyświetl plik

@ -1,5 +1,6 @@
import os.path
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied
from django.http import HttpResponseBadRequest, JsonResponse
from django.shortcuts import get_object_or_404
@ -10,13 +11,13 @@ from django.views.decorators.vary import vary_on_headers
from django.views.generic.base import TemplateView, View
from wagtail.admin.views.generic import PermissionCheckedMixin
from wagtail.models import UploadedFile
class AddView(PermissionCheckedMixin, TemplateView):
# subclasses need to provide:
# - permission_policy
# - template_name
# - upload_model
# - edit_object_url_name
# - delete_object_url_name
@ -152,10 +153,12 @@ class AddView(PermissionCheckedMixin, TemplateView):
return JsonResponse(self.get_invalid_response_data(form))
else:
# Some other field of the form has failed validation, e.g. a required metadata field
# on a custom image model. Store the object as an upload_model instance instead and
# on a custom image model. Store the object as an UploadedFile instance instead and
# present the edit form so that it will become a proper object when successfully filled in
self.upload_object = self.upload_model.objects.create(
file=self.request.FILES["files[]"], uploaded_by_user=self.request.user
self.upload_object = UploadedFile.objects.create(
for_content_type=ContentType.objects.get_for_model(self.get_model()),
file=self.request.FILES["files[]"],
uploaded_by_user=self.request.user,
)
self.object = self.model(
title=self.request.FILES["files[]"].name,
@ -297,7 +300,6 @@ class CreateFromUploadView(View):
# subclasses need to provide:
# - edit_upload_url_name
# - delete_upload_url_name
# - upload_model
# - upload_pk_url_kwarg
# - edit_upload_form_prefix
# - context_object_id_name
@ -320,7 +322,11 @@ class CreateFromUploadView(View):
self.model = self.get_model()
self.form_class = self.get_edit_form_class()
self.upload = get_object_or_404(self.upload_model, id=upload_id)
self.upload = get_object_or_404(
UploadedFile,
id=upload_id,
for_content_type=ContentType.objects.get_for_model(self.model),
)
if self.upload.uploaded_by_user != request.user:
raise PermissionDenied
@ -369,14 +375,17 @@ class CreateFromUploadView(View):
class DeleteUploadView(View):
# subclasses need to provide:
# - upload_model
# - upload_pk_url_kwarg
http_method_names = ["post"]
def post(self, request, *args, **kwargs):
upload_id = kwargs[self.upload_pk_url_kwarg]
upload = get_object_or_404(self.upload_model, id=upload_id)
upload = get_object_or_404(
UploadedFile,
id=upload_id,
for_content_type=ContentType.objects.get_for_model(self.get_model()),
)
if upload.uploaded_by_user != request.user:
raise PermissionDenied

Wyświetl plik

@ -16,7 +16,7 @@ urlpatterns = [
path("multiple/add/", multiple.AddView.as_view(), name="add_multiple"),
path("multiple/<int:doc_id>/", multiple.EditView.as_view(), name="edit_multiple"),
path(
"multiple/create_from_uploaded_document/<int:uploaded_document_id>/",
"multiple/create_from_uploaded_document/<int:uploaded_file_id>/",
multiple.CreateFromUploadedDocumentView.as_view(),
name="create_multiple_from_uploaded_document",
),
@ -26,7 +26,7 @@ urlpatterns = [
name="delete_multiple",
),
path(
"multiple/delete_upload/<int:uploaded_document_id>/",
"multiple/delete_upload/<int:uploaded_file_id>/",
multiple.DeleteUploadView.as_view(),
name="delete_upload_multiple",
),

Wyświetl plik

@ -0,0 +1,16 @@
# Generated by Django 5.0.1 on 2024-01-30 18:19
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('wagtaildocs', '0012_uploadeddocument'),
]
operations = [
migrations.DeleteModel(
name='UploadedDocument',
),
]

Wyświetl plik

@ -215,23 +215,3 @@ class Document(AbstractDocument):
# provides args: request
document_served = Signal()
class UploadedDocument(models.Model):
"""
Temporary storage for documents uploaded through the multiple doc uploader, when validation
rules (e.g. required metadata fields) prevent creating a Document object from the document file
alone. In this case, the document file is stored against this model, to be turned into a
Document object once the full form has been filled in.
"""
file = models.FileField(upload_to="uploaded_documents", max_length=200)
uploaded_by_user = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=_("uploaded by user"),
null=True,
blank=True,
editable=False,
on_delete=models.SET_NULL,
)
uploaded_by_user.wagtail_reference_index_ignore = True

Wyświetl plik

@ -2,6 +2,7 @@ import json
from unittest import mock
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase, TransactionTestCase
from django.test.utils import override_settings
@ -12,7 +13,13 @@ from django.utils.http import urlencode
from wagtail.admin.admin_url_finder import AdminURLFinder
from wagtail.documents import get_document_model, models
from wagtail.documents.tests.utils import get_test_document_file
from wagtail.models import Collection, GroupCollectionPermission, Page, ReferenceIndex
from wagtail.models import (
Collection,
GroupCollectionPermission,
Page,
ReferenceIndex,
UploadedFile,
)
from wagtail.test.testapp.models import (
CustomDocument,
CustomDocumentWithAuthor,
@ -1277,15 +1284,16 @@ class TestMultipleCustomDocumentUploaderWithRequiredField(TestMultipleDocumentUp
def setUp(self):
super().setUp()
# Create an UploadedDocument for running tests on
self.uploaded_document = models.UploadedDocument.objects.create(
# Create an UploadedFile for running tests on
self.uploaded_document = UploadedFile.objects.create(
for_content_type=ContentType.objects.get_for_model(get_document_model()),
file=get_test_document_file(),
uploaded_by_user=self.user,
)
def test_add_post(self):
"""
This tests that a POST request to the add view saves the document as an UploadedDocument
This tests that a POST request to the add view saves the document as an UploadedFile
and returns an edit form
"""
response = self.client.post(
@ -1326,11 +1334,11 @@ class TestMultipleCustomDocumentUploaderWithRequiredField(TestMultipleDocumentUp
# Check JSON
response_json = json.loads(response.content.decode())
self.assertIn("uploaded_document_id", response_json)
self.assertIn("uploaded_file_id", response_json)
self.assertIn("form", response_json)
self.assertIn("success", response_json)
self.assertEqual(
response_json["uploaded_document_id"],
response_json["uploaded_file_id"],
response.context["uploaded_document"].id,
)
self.assertTrue(response_json["success"])
@ -1363,10 +1371,10 @@ class TestMultipleCustomDocumentUploaderWithRequiredField(TestMultipleDocumentUp
# Check JSON
response_json = json.loads(response.content.decode())
self.assertIn("uploaded_document_id", response_json)
self.assertIn("uploaded_file_id", response_json)
self.assertIn("form", response_json)
self.assertEqual(
response_json["uploaded_document_id"],
response_json["uploaded_file_id"],
response.context["uploaded_document"].id,
)
self.assertTrue(response_json["success"])
@ -1419,11 +1427,11 @@ class TestMultipleCustomDocumentUploaderWithRequiredField(TestMultipleDocumentUp
# Check JSON
response_json = json.loads(response.content.decode())
self.assertIn("uploaded_document_id", response_json)
self.assertIn("uploaded_file_id", response_json)
self.assertIn("form", response_json)
self.assertIn("success", response_json)
self.assertEqual(
response_json["uploaded_document_id"],
response_json["uploaded_file_id"],
response.context["uploaded_document"].id,
)
self.assertTrue(response_json["success"])
@ -1438,10 +1446,10 @@ class TestMultipleCustomDocumentUploaderWithRequiredField(TestMultipleDocumentUp
def test_create_from_upload_invalid_post(self):
"""
Posting an invalid form to the create_from_uploaded_document view throws a validation error
and leaves the UploadedDocument intact
and leaves the UploadedFile intact
"""
doc_count_before = CustomDocumentWithAuthor.objects.count()
uploaded_doc_count_before = models.UploadedDocument.objects.count()
uploaded_doc_count_before = UploadedFile.objects.count()
# Send request
response = self.client.post(
@ -1459,9 +1467,9 @@ class TestMultipleCustomDocumentUploaderWithRequiredField(TestMultipleDocumentUp
)
doc_count_after = CustomDocumentWithAuthor.objects.count()
uploaded_doc_count_after = models.UploadedDocument.objects.count()
uploaded_doc_count_after = UploadedFile.objects.count()
# no changes to document / UploadedDocument count
# no changes to document / UploadedFile count
self.assertEqual(doc_count_after, doc_count_before)
self.assertEqual(uploaded_doc_count_after, uploaded_doc_count_before)
@ -1497,7 +1505,7 @@ class TestMultipleCustomDocumentUploaderWithRequiredField(TestMultipleDocumentUp
Posting a valid form to the create_from_uploaded_document view will create the document
"""
doc_count_before = CustomDocumentWithAuthor.objects.count()
uploaded_doc_count_before = models.UploadedDocument.objects.count()
uploaded_doc_count_before = UploadedFile.objects.count()
# Send request
response = self.client.post(
@ -1519,7 +1527,7 @@ class TestMultipleCustomDocumentUploaderWithRequiredField(TestMultipleDocumentUp
)
doc_count_after = CustomDocumentWithAuthor.objects.count()
uploaded_doc_count_after = models.UploadedDocument.objects.count()
uploaded_doc_count_after = UploadedFile.objects.count()
# Check response
self.assertEqual(response.status_code, 200)
@ -1530,7 +1538,7 @@ class TestMultipleCustomDocumentUploaderWithRequiredField(TestMultipleDocumentUp
self.assertIn("doc_id", response_json)
self.assertTrue(response_json["success"])
# Document should have been created, UploadedDocument deleted
# Document should have been created, UploadedFile deleted
self.assertEqual(doc_count_after, doc_count_before + 1)
self.assertEqual(uploaded_doc_count_after, uploaded_doc_count_before - 1)
@ -1544,7 +1552,7 @@ class TestMultipleCustomDocumentUploaderWithRequiredField(TestMultipleDocumentUp
def test_delete_uploaded_document(self):
"""
This tests that a POST request to the delete view deletes the UploadedDocument
This tests that a POST request to the delete view deletes the UploadedFile
"""
# Send request
response = self.client.post(
@ -1559,9 +1567,7 @@ class TestMultipleCustomDocumentUploaderWithRequiredField(TestMultipleDocumentUp
# Make sure the document is deleted
self.assertFalse(
models.UploadedDocument.objects.filter(
id=self.uploaded_document.id
).exists()
UploadedFile.objects.filter(id=self.uploaded_document.id).exists()
)
# Check JSON

Wyświetl plik

@ -12,14 +12,12 @@ from wagtail.admin.views.generic.multiple_upload import EditView as BaseEditView
from .. import get_document_model
from ..forms import get_document_form, get_document_multi_form
from ..models import UploadedDocument
from ..permissions import permission_policy
class AddView(BaseAddView):
permission_policy = permission_policy
template_name = "wagtaildocs/multiple/add.html"
upload_model = UploadedDocument
edit_object_url_name = "wagtaildocs:edit_multiple"
delete_object_url_name = "wagtaildocs:delete_multiple"
@ -31,7 +29,7 @@ class AddView(BaseAddView):
delete_upload_url_name = "wagtaildocs:delete_upload_multiple"
edit_upload_form_prefix = "uploaded-document"
context_upload_name = "uploaded_document"
context_upload_id_name = "uploaded_document_id"
context_upload_id_name = "uploaded_file_id"
def get_model(self):
return get_document_model()
@ -90,8 +88,7 @@ class DeleteView(BaseDeleteView):
class CreateFromUploadedDocumentView(BaseCreateFromUploadView):
edit_upload_url_name = "wagtaildocs:create_multiple_from_uploaded_document"
delete_upload_url_name = "wagtaildocs:delete_upload_multiple"
upload_model = UploadedDocument
upload_pk_url_kwarg = "uploaded_document_id"
upload_pk_url_kwarg = "uploaded_file_id"
edit_upload_form_prefix = "uploaded-document"
context_object_id_name = "doc_id"
context_upload_name = "uploaded_document"
@ -118,5 +115,7 @@ class CreateFromUploadedDocumentView(BaseCreateFromUploadView):
class DeleteUploadView(BaseDeleteUploadView):
upload_model = UploadedDocument
upload_pk_url_kwarg = "uploaded_document_id"
upload_pk_url_kwarg = "uploaded_file_id"
def get_model(self):
return get_document_model()

Wyświetl plik

@ -24,7 +24,7 @@ urlpatterns = [
path("multiple/add/", multiple.AddView.as_view(), name="add_multiple"),
path("multiple/<int:image_id>/", multiple.EditView.as_view(), name="edit_multiple"),
path(
"multiple/create_from_uploaded_image/<int:uploaded_image_id>/",
"multiple/create_from_uploaded_image/<int:uploaded_file_id>/",
multiple.CreateFromUploadedImageView.as_view(),
name="create_multiple_from_uploaded_image",
),
@ -34,7 +34,7 @@ urlpatterns = [
name="delete_multiple",
),
path(
"multiple/delete_upload/<int:uploaded_image_id>/",
"multiple/delete_upload/<int:uploaded_file_id>/",
multiple.DeleteUploadView.as_view(),
name="delete_upload_multiple",
),

Wyświetl plik

@ -0,0 +1,16 @@
# Generated by Django 5.0.1 on 2024-01-30 18:19
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('wagtailimages', '0025_alter_image_file_alter_rendition_file'),
]
operations = [
migrations.DeleteModel(
name='UploadedImage',
),
]

Wyświetl plik

@ -1331,23 +1331,3 @@ class Rendition(AbstractRendition):
class Meta:
unique_together = (("image", "filter_spec", "focal_point_key"),)
class UploadedImage(models.Model):
"""
Temporary storage for images uploaded through the multiple image uploader, when validation rules (e.g.
required metadata fields) prevent creating an Image object from the image file alone. In this case,
the image file is stored against this model, to be turned into an Image object once the full form
has been filled in.
"""
file = models.ImageField(upload_to="uploaded_images", max_length=200)
uploaded_by_user = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=_("uploaded by user"),
null=True,
blank=True,
editable=False,
on_delete=models.SET_NULL,
)
uploaded_by_user.wagtail_reference_index_ignore = True

Wyświetl plik

@ -4,6 +4,7 @@ import urllib
from django.conf import settings
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
from django.core.files.uploadedfile import SimpleUploadedFile, TemporaryUploadedFile
from django.template.defaultfilters import filesizeformat
from django.template.loader import render_to_string
@ -16,12 +17,12 @@ from django.utils.safestring import mark_safe
from wagtail.admin.admin_url_finder import AdminURLFinder
from wagtail.images import get_image_model
from wagtail.images.models import UploadedImage
from wagtail.images.utils import generate_signature
from wagtail.models import (
Collection,
GroupCollectionPermission,
Page,
UploadedFile,
get_root_collection_id,
)
from wagtail.test.testapp.models import (
@ -2886,7 +2887,7 @@ class TestMultipleImageUploaderWithCustomImageModel(WagtailTestUtils, TestCase):
def test_unique_together_validation_error(self):
"""
If unique_together validation fails, create an UploadedImage and return a form so the
If unique_together validation fails, create an UploadedFile and return a form so the
user can fix it
"""
root_collection = Collection.get_first_root_node()
@ -2895,7 +2896,7 @@ class TestMultipleImageUploaderWithCustomImageModel(WagtailTestUtils, TestCase):
self.image.save()
image_count_before = CustomImage.objects.count()
uploaded_image_count_before = UploadedImage.objects.count()
uploaded_image_count_before = UploadedFile.objects.count()
response = self.client.post(
reverse("wagtailimages:add_multiple"),
@ -2908,9 +2909,9 @@ class TestMultipleImageUploaderWithCustomImageModel(WagtailTestUtils, TestCase):
)
image_count_after = CustomImage.objects.count()
uploaded_image_count_after = UploadedImage.objects.count()
uploaded_image_count_after = UploadedFile.objects.count()
# an UploadedImage should have been created now, but not a CustomImage
# an UploadedFile should have been created now, but not a CustomImage
self.assertEqual(image_count_after, image_count_before)
self.assertEqual(uploaded_image_count_after, uploaded_image_count_before + 1)
@ -3034,8 +3035,9 @@ class TestMultipleImageUploaderWithCustomRequiredFields(WagtailTestUtils, TestCa
def setUp(self):
self.user = self.login()
# Create an UploadedImage for running tests on
self.uploaded_image = UploadedImage.objects.create(
# Create an UploadedFile for running tests on
self.uploaded_image = UploadedFile.objects.create(
for_content_type=ContentType.objects.get_for_model(get_image_model()),
file=get_test_image_file(),
uploaded_by_user=self.user,
)
@ -3053,11 +3055,11 @@ class TestMultipleImageUploaderWithCustomRequiredFields(WagtailTestUtils, TestCa
def test_add_post(self):
"""
A POST request to the add view should create an UploadedImage rather than an image,
A POST request to the add view should create an UploadedFile rather than an image,
as we do not have enough data to pass CustomImageWithAuthor's validation yet
"""
image_count_before = CustomImageWithAuthor.objects.count()
uploaded_image_count_before = UploadedImage.objects.count()
uploaded_image_count_before = UploadedFile.objects.count()
response = self.client.post(
reverse("wagtailimages:add_multiple"),
@ -3069,9 +3071,9 @@ class TestMultipleImageUploaderWithCustomRequiredFields(WagtailTestUtils, TestCa
)
image_count_after = CustomImageWithAuthor.objects.count()
uploaded_image_count_after = UploadedImage.objects.count()
uploaded_image_count_after = UploadedFile.objects.count()
# an UploadedImage should have been created now, but not a CustomImageWithAuthor
# an UploadedFile should have been created now, but not a CustomImageWithAuthor
self.assertEqual(image_count_after, image_count_before)
self.assertEqual(uploaded_image_count_after, uploaded_image_count_before + 1)
@ -3137,10 +3139,10 @@ class TestMultipleImageUploaderWithCustomRequiredFields(WagtailTestUtils, TestCa
def test_create_from_upload_invalid_post(self):
"""
Posting an invalid form to the create_from_uploaded_image view throws a validation error and leaves the
UploadedImage intact
UploadedFile intact
"""
image_count_before = CustomImageWithAuthor.objects.count()
uploaded_image_count_before = UploadedImage.objects.count()
uploaded_image_count_before = UploadedFile.objects.count()
# Send request
response = self.client.post(
@ -3156,9 +3158,9 @@ class TestMultipleImageUploaderWithCustomRequiredFields(WagtailTestUtils, TestCa
)
image_count_after = CustomImageWithAuthor.objects.count()
uploaded_image_count_after = UploadedImage.objects.count()
uploaded_image_count_after = UploadedFile.objects.count()
# no changes to image / UploadedImage count
# no changes to image / UploadedFile count
self.assertEqual(image_count_after, image_count_before)
self.assertEqual(uploaded_image_count_after, uploaded_image_count_before)
@ -3194,7 +3196,7 @@ class TestMultipleImageUploaderWithCustomRequiredFields(WagtailTestUtils, TestCa
Posting a valid form to the create_from_uploaded_image view will create the image
"""
image_count_before = CustomImageWithAuthor.objects.count()
uploaded_image_count_before = UploadedImage.objects.count()
uploaded_image_count_before = UploadedFile.objects.count()
# Send request
response = self.client.post(
@ -3212,7 +3214,7 @@ class TestMultipleImageUploaderWithCustomRequiredFields(WagtailTestUtils, TestCa
)
image_count_after = CustomImageWithAuthor.objects.count()
uploaded_image_count_after = UploadedImage.objects.count()
uploaded_image_count_after = UploadedFile.objects.count()
# Check response
self.assertEqual(response.status_code, 200)
@ -3223,7 +3225,7 @@ class TestMultipleImageUploaderWithCustomRequiredFields(WagtailTestUtils, TestCa
self.assertIn("image_id", response_json)
self.assertTrue(response_json["success"])
# Image should have been created, UploadedImage deleted
# Image should have been created, UploadedFile deleted
self.assertEqual(image_count_after, image_count_before + 1)
self.assertEqual(uploaded_image_count_after, uploaded_image_count_before - 1)
@ -3239,7 +3241,7 @@ class TestMultipleImageUploaderWithCustomRequiredFields(WagtailTestUtils, TestCa
def test_delete_uploaded_image(self):
"""
This tests that a POST request to the delete view deletes the UploadedImage
This tests that a POST request to the delete view deletes the UploadedFile
"""
# Send request
response = self.client.post(
@ -3254,7 +3256,7 @@ class TestMultipleImageUploaderWithCustomRequiredFields(WagtailTestUtils, TestCa
# Make sure the image is deleted
self.assertFalse(
UploadedImage.objects.filter(id=self.uploaded_image.id).exists()
UploadedFile.objects.filter(id=self.uploaded_image.id).exists()
)
# Check JSON

Wyświetl plik

@ -15,7 +15,6 @@ from wagtail.admin.views.generic.multiple_upload import EditView as BaseEditView
from wagtail.images import get_image_model
from wagtail.images.fields import get_allowed_image_extensions
from wagtail.images.forms import get_image_form, get_image_multi_form
from wagtail.images.models import UploadedImage
from wagtail.images.permissions import ImagesPermissionPolicyGetter, permission_policy
from wagtail.images.utils import find_image_duplicates
@ -23,7 +22,6 @@ from wagtail.images.utils import find_image_duplicates
class AddView(BaseAddView):
permission_policy = ImagesPermissionPolicyGetter()
template_name = "wagtailimages/multiple/add.html"
upload_model = UploadedImage
edit_object_url_name = "wagtailimages:edit_multiple"
delete_object_url_name = "wagtailimages:delete_multiple"
@ -35,7 +33,7 @@ class AddView(BaseAddView):
delete_upload_url_name = "wagtailimages:delete_upload_multiple"
edit_upload_form_prefix = "uploaded-image"
context_upload_name = "uploaded_image"
context_upload_id_name = "uploaded_image_id"
context_upload_id_name = "uploaded_file_id"
def get_model(self):
return get_image_model()
@ -131,8 +129,7 @@ class DeleteView(BaseDeleteView):
class CreateFromUploadedImageView(BaseCreateFromUploadView):
edit_upload_url_name = "wagtailimages:create_multiple_from_uploaded_image"
delete_upload_url_name = "wagtailimages:delete_upload_multiple"
upload_model = UploadedImage
upload_pk_url_kwarg = "uploaded_image_id"
upload_pk_url_kwarg = "uploaded_file_id"
edit_upload_form_prefix = "uploaded-image"
context_object_id_name = "image_id"
context_upload_name = "uploaded_image"
@ -160,5 +157,7 @@ class CreateFromUploadedImageView(BaseCreateFromUploadView):
class DeleteUploadView(BaseDeleteUploadView):
upload_model = UploadedImage
upload_pk_url_kwarg = "uploaded_image_id"
upload_pk_url_kwarg = "uploaded_file_id"
def get_model(self):
return get_image_model()

Wyświetl plik

@ -0,0 +1,53 @@
# Generated by Django 4.2.7 on 2024-02-12 21:04
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("contenttypes", "0002_remove_content_type_name"),
("wagtailcore", "0092_alter_collectionviewrestriction_password_and_more"),
]
operations = [
migrations.CreateModel(
name="UploadedFile",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("file", models.FileField(max_length=200, upload_to="wagtail_uploads")),
(
"for_content_type",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="uploads",
to="contenttypes.contenttype",
verbose_name="for content type",
),
),
(
"uploaded_by_user",
models.ForeignKey(
blank=True,
editable=False,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to=settings.AUTH_USER_MODEL,
verbose_name="uploaded by user",
),
),
],
),
]

Wyświetl plik

@ -100,16 +100,6 @@ from .audit_log import ( # noqa: F401
LogEntryQuerySet,
ModelLogEntry,
)
from .collections import ( # noqa: F401
BaseCollectionManager,
Collection,
CollectionManager,
CollectionMember,
CollectionViewRestriction,
GroupCollectionPermission,
GroupCollectionPermissionManager,
get_root_collection_id,
)
from .copying import _copy, _copy_m2m_relations, _extract_field_data # noqa: F401
from .i18n import ( # noqa: F401
BootstrapTranslatableMixin,
@ -120,6 +110,17 @@ from .i18n import ( # noqa: F401
bootstrap_translatable_model,
get_translatable_models,
)
from .media import ( # noqa: F401
BaseCollectionManager,
Collection,
CollectionManager,
CollectionMember,
CollectionViewRestriction,
GroupCollectionPermission,
GroupCollectionPermissionManager,
UploadedFile,
get_root_collection_id,
)
from .reference_index import ReferenceIndex # noqa: F401
from .sites import Site, SiteManager, SiteRootPath # noqa: F401
from .specific import SpecificMixin

Wyświetl plik

@ -1,4 +1,6 @@
from django.conf import settings
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.utils.html import format_html
from django.utils.safestring import mark_safe
@ -188,3 +190,29 @@ class GroupCollectionPermission(models.Model):
unique_together = ("group", "collection", "permission")
verbose_name = _("group collection permission")
verbose_name_plural = _("group collection permissions")
class UploadedFile(models.Model):
"""
Temporary storage for media fields uploaded through the multiple image/document uploader.
When validation rules (e.g. required metadata fields) prevent creating an Image/Document object from the file alone.
In this case, the file is stored against this model, to be turned into an Image/Document object once the full form
has been filled in.
"""
for_content_type = models.ForeignKey(
ContentType,
verbose_name=_("for content type"),
related_name="uploads",
on_delete=models.CASCADE,
null=True,
)
file = models.FileField(upload_to="wagtail_uploads", max_length=200)
uploaded_by_user = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name=_("uploaded by user"),
null=True,
blank=True,
editable=False,
on_delete=models.SET_NULL,
)

Wyświetl plik

@ -15,7 +15,7 @@ import wagtail.contrib.table_block.blocks
import wagtail.fields
import wagtail.images.blocks
import wagtail.images.models
import wagtail.models.collections
import wagtail.models.media
import wagtail.search.index
import wagtail.test.testapp.models
@ -1043,7 +1043,7 @@ class Migration(migrations.Migration):
(
"collection",
models.ForeignKey(
default=wagtail.models.collections.get_root_collection_id,
default=wagtail.models.media.get_root_collection_id,
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="wagtailcore.collection",
@ -2244,7 +2244,7 @@ class Migration(migrations.Migration):
(
"collection",
models.ForeignKey(
default=wagtail.models.collections.get_root_collection_id,
default=wagtail.models.media.get_root_collection_id,
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="wagtailcore.collection",
@ -2341,7 +2341,7 @@ class Migration(migrations.Migration):
(
"collection",
models.ForeignKey(
default=wagtail.models.collections.get_root_collection_id,
default=wagtail.models.media.get_root_collection_id,
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="wagtailcore.collection",
@ -2622,7 +2622,7 @@ class Migration(migrations.Migration):
(
"collection",
models.ForeignKey(
default=wagtail.models.collections.get_root_collection_id,
default=wagtail.models.media.get_root_collection_id,
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="wagtailcore.collection",
@ -3891,7 +3891,7 @@ class Migration(migrations.Migration):
(
"collection",
models.ForeignKey(
default=wagtail.models.collections.get_root_collection_id,
default=wagtail.models.media.get_root_collection_id,
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="wagtailcore.collection",
@ -3936,7 +3936,7 @@ class Migration(migrations.Migration):
(
"collection",
models.ForeignKey(
default=wagtail.models.collections.get_root_collection_id,
default=wagtail.models.media.get_root_collection_id,
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="wagtailcore.collection",
@ -4055,7 +4055,7 @@ class Migration(migrations.Migration):
(
"collection",
models.ForeignKey(
default=wagtail.models.collections.get_root_collection_id,
default=wagtail.models.media.get_root_collection_id,
on_delete=django.db.models.deletion.CASCADE,
related_name="+",
to="wagtailcore.collection",

Wyświetl plik

@ -0,0 +1,31 @@
# Generated by Django 4.2.7 on 2024-02-12 22:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("tests", "0033_customcopyformpage"),
]
operations = [
migrations.CreateModel(
name="ModelWithVerboseName",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
],
options={
"verbose_name": "Custom verbose name",
"verbose_name_plural": "Custom verbose names",
},
),
]

Wyświetl plik

@ -1921,6 +1921,15 @@ class TableBlockStreamPage(Page):
content_panels = [FieldPanel("table")]
class ModelWithVerboseName(ClusterableModel):
class Meta:
verbose_name = "Custom verbose name"
verbose_name_plural = "Custom verbose names"
register_snippet(ModelWithVerboseName)
class UserProfile(models.Model):
# Wagtail's schema must be able to coexist alongside a custom UserProfile model
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

Wyświetl plik

@ -1,5 +1,4 @@
import itertools
import re
from django import template
@ -85,7 +84,10 @@ def format_permissions(permission_bound_field):
checkbox = checkboxes_by_id[perm.id]
# identify the main categories of permission, and assign to
# the relevant dict key, else bung in the 'custom_perms' list
permission_action = perm.codename.split("_")[0]
permission_action = perm.codename.split("_", maxsplit=1)
permission_action = permission_action[permission_action[0].lower() == "can"]
permission_action = permission_action.rsplit(maxsplit=1)[0]
if permission_action in main_permission_names:
if permission_action in extra_perms_exist:
extra_perms_exist[permission_action] = True
@ -98,9 +100,7 @@ def format_permissions(permission_bound_field):
custom_perms.append(
{
"perm": perm,
"name": re.sub(
f"{perm.content_type.name}$", "", perm.name, flags=re.I
).strip(),
"name": f"Can {permission_action}",
"selected": checkbox.data["selected"],
}
)

Wyświetl plik

@ -1567,6 +1567,35 @@ class TestGroupCreateView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
# Should not show inputs for publish permissions on models without DraftStateMixin
self.assertNotInHTML("Can publish advert", html)
def test_view_permission_if_model_has_verbose(self):
"""
Tests for bug #10982
https://github.com/wagtail/wagtail/issues/10982
Ensures Model Name or Verbose Name is stripped from Custom Permissions before being displayed
A Model "ModelWithVerboseName" is added to wagtail.test.testapp.models with verbose_name = "Custom Verbose Name"
"""
response = self.get()
self.assertContains(response, "Can view", msg_prefix="No Can view permission")
self.assertNotContains(
response,
"Can view model with verbose name",
msg_prefix=" Model Name not stripped from Can view permission",
)
self.assertNotContains(
response,
"Can view custom verbose name",
msg_prefix="Verbose Name of ModelWithVerboseName not stripped from Can view permission",
)
pattern = r'Can\sview\s\[.*?"'
self.assertNotRegex(
response.content.decode(),
pattern,
msg="Model Name not stripped from custom permissions",
)
class TestGroupEditView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
def setUp(self):