kopia lustrzana https://github.com/wagtail/wagtail
Porównaj commity
35 Commity
007c78c276
...
b2f9c699cf
Autor | SHA1 | Data |
---|---|---|
sage | b2f9c699cf | |
Matt Westcott | 196cb02d10 | |
Matt Westcott | 1874350fbf | |
Storm Heg | 2031eb3d24 | |
Matt Westcott | 4ac42a723f | |
Matt Westcott | ef57f9b2c9 | |
Matt Westcott | 955703fba6 | |
Matt Westcott | 42b7c9bcde | |
Matt Westcott | 9d24ac4e39 | |
Jake Howard | ee57f6d4dc | |
Matt Westcott | 932402fd28 | |
Matt Westcott | 7de6872277 | |
Matt Westcott | 95d23fdf7d | |
Matt Westcott | 72edc09851 | |
Matt Westcott | fac768c076 | |
Matt Westcott | 81a11d63c6 | |
Matt Westcott | 617e5129c5 | |
Thibaud Colas | cae0002afe | |
Sage Abdullah | 08ee15a358 | |
Sage Abdullah | b8dd7f484f | |
Sage Abdullah | 56e69bc3ea | |
Jake Howard | afbafd657d | |
Benjamin Bach | b266e54ba9 | |
Matt Westcott | 763c990490 | |
Matt Westcott | 207d5dafd5 | |
Sage Abdullah | c3a52a6fdb | |
Sage Abdullah | 4302bed1b1 | |
Sage Abdullah | ae28020195 | |
rohitsrma | 6f28aa9d8b | |
rohitsrma | 3d63d0da4f | |
Sage Abdullah | ef3d01f55f | |
Sage Abdullah | da8b572069 | |
Sage Abdullah | acb47405db | |
Sage Abdullah | 23301e826e | |
Sage Abdullah | fb5d1162f6 |
|
@ -6,10 +6,14 @@ Changelog
|
|||
|
||||
* Optimize and consolidate redirects report view into the index view (Jake Howard, Dan Braghis)
|
||||
* Support a `HOSTNAMES` parameter on `WAGTAILFRONTENDCACHE` to define which hostnames a backend should respond to (Jake Howard, sponsored by Oxfam America)
|
||||
* Refactor redirects edit view to use the generic `EditView` and breadcrumbs (Rohit Sharma)
|
||||
* Fix: Make `WAGTAILIMAGES_CHOOSER_PAGE_SIZE` setting functional again (Rohit Sharma)
|
||||
* Fix: Enable `richtext` template tag to convert lazy translation values (Benjamin Bach)
|
||||
* Docs: Remove duplicate section on frontend caching proxies from performance page (Jake Howard)
|
||||
* Maintenance: Use `DjangoJSONEncoder` instead of custom `LazyStringEncoder` to serialize Draftail config (Sage Abdullah)
|
||||
|
||||
|
||||
6.1 (xx.xx.xxxx) - IN DEVELOPMENT
|
||||
6.1 (01.05.2024)
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
* Refine wording of page & collection privacy using password is a shared password and should not be used for secure content (Rohit Sharma, Jake Howard)
|
||||
|
@ -49,6 +53,7 @@ Changelog
|
|||
* Populate django-treebeard cache during page routing to improve performance of `get_parent` (Nigel van Keulen)
|
||||
* Add a new user profile preference to configure user interface information density (Thibaud Colas)
|
||||
* Add additional field types to Elasticsearch mapping (scott-8)
|
||||
* Fix: CVE-2024-32882: Permission check bypass when editing a model with per-field restrictions through `wagtail.contrib.settings` or `ModelViewSet` (Ben Morse, Joshua Munn, Jake Howard, Sage Abdullah)
|
||||
* 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)
|
||||
|
@ -69,6 +74,8 @@ Changelog
|
|||
* Fix: Improve exception handling when generating image renditions concurrently (Andy Babic)
|
||||
* Fix: Respect `WAGTAIL_ALLOW_UNICODE_SLUGS` setting when auto-generating slugs (LB (Ben) Johnston)
|
||||
* Fix: Use correct URL when redirecting back to page search results after an AJAX search (Sage Abdullah)
|
||||
* Fix: Reinstate missing static files in style guide (Sage Abdullah)
|
||||
* Fix: Provide `convert_mariadb_uuids` management command to assist with upgrading to Django 5.0+ on MariaDB (Matt Westcott)
|
||||
* 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 content and page names to their US spelling instead of UK spelling (Victoria Poromon)
|
||||
|
@ -111,11 +118,15 @@ Changelog
|
|||
* Maintenance: Refactor the Django port of `urlify` to use TypeScript, officially deprecate `window.URLify` global util (LB (Ben) Johnston)
|
||||
|
||||
|
||||
6.0.3 (xx.xx.xxxx) - IN DEVELOPMENT
|
||||
6.0.3 (01.05.2024)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Fix: CVE-2024-32882: Permission check bypass when editing a model with per-field restrictions through `wagtail.contrib.settings` or `ModelViewSet` (Ben Morse, Joshua Munn, Jake Howard, Sage Abdullah)
|
||||
* Fix: Respect `WAGTAIL_ALLOW_UNICODE_SLUGS` setting when auto-generating slugs (LB (Ben) Johnston)
|
||||
* Fix: Use correct URL when redirecting back to page search results after an AJAX search (Sage Abdullah)
|
||||
* Fix: Reinstate missing static files in style guide (Sage Abdullah)
|
||||
* Fix: Provide `convert_mariadb_uuids` management command to assist with upgrading to Django 5.0+ on MariaDB (Matt Westcott)
|
||||
* Fix: Fix generic CopyView for models with primary keys that need to be quoted (Sage Abdullah)
|
||||
|
||||
|
||||
6.0.2 (03.04.2024)
|
||||
|
@ -309,11 +320,12 @@ Changelog
|
|||
* Maintenance: Remove support for Django 4.1 and below (Sage Abdullah)
|
||||
|
||||
|
||||
5.2.5 (xx.xx.xxxx) - IN DEVELOPMENT
|
||||
5.2.5 (01.05.2024)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* Fix: Respect `WAGTAIL_ALLOW_UNICODE_SLUGS` setting when auto-generating slugs (LB (Ben) Johnston)
|
||||
* Fix: Use correct URL when redirecting back to page search results after an AJAX search (Sage Abdullah)
|
||||
* Fix: Provide `convert_mariadb_uuids` management command to assist with upgrading to Django 5.0+ on MariaDB (Matt Westcott)
|
||||
|
||||
|
||||
5.2.4 (03.04.2024)
|
||||
|
|
|
@ -223,6 +223,10 @@ ul.listing {
|
|||
|
||||
.title {
|
||||
word-break: break-word;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: theme('spacing.2');
|
||||
|
||||
.title-wrapper,
|
||||
h2 {
|
||||
|
@ -231,6 +235,7 @@ ul.listing {
|
|||
gap: theme('spacing.2');
|
||||
margin: 0;
|
||||
vertical-align: middle;
|
||||
align-items: center;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
|
@ -242,11 +247,6 @@ ul.listing {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon-folder {
|
||||
margin: 3px 0.3em 0 0;
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
|
@ -265,36 +265,6 @@ ul.listing {
|
|||
}
|
||||
}
|
||||
|
||||
&--inline-actions td.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
|
||||
.title-wrapper {
|
||||
margin-inline-end: 2.5em;
|
||||
}
|
||||
|
||||
.w-status {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&--inline-actions .actions {
|
||||
display: inline-block;
|
||||
margin-top: 0;
|
||||
vertical-align: inherit;
|
||||
|
||||
li {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.button {
|
||||
vertical-align: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.moderate-actions form {
|
||||
float: inline-start;
|
||||
margin: 0 1em 1em 0;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
border-radius: 2px;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
word-break: normal;
|
||||
// stylelint-disable-next-line property-disallowed-list
|
||||
text-transform: uppercase;
|
||||
padding: 0 0.5em;
|
||||
|
|
|
@ -123,7 +123,7 @@ urlpatterns = [
|
|||
[django-sendfile](https://github.com/johnsensible/django-sendfile) offloads the job of transferring the image data to the web
|
||||
server instead of serving it directly from the Django application. This could
|
||||
greatly reduce server load in situations where your site has many images being
|
||||
downloaded but you're unable to use a [](caching_proxy) or a CDN.
|
||||
downloaded but you're unable to use a [caching proxy](performance_frontend_caching) or a CDN.
|
||||
|
||||
You first need to install and configure django-sendfile and configure your
|
||||
web server to use it. If you haven't done this already, please refer to the
|
||||
|
|
|
@ -60,16 +60,18 @@ The same can be achieved in Python using [`generate_image_url`](dynamic_image_ur
|
|||
|
||||
When using a queryset to render a list of images or objects with images, you can [prefetch the renditions](prefetching_image_renditions) needed with a single additional query. For long lists of items, or where multiple renditions are used for each item, this can provide a significant boost to performance.
|
||||
|
||||
(performance_page_urls)=
|
||||
(performance_frontend_caching)=
|
||||
|
||||
## Frontend caching
|
||||
## Frontend caching proxy
|
||||
|
||||
Many websites use a frontend cache such as Varnish, Squid, Cloudflare or CloudFront to gain extra performance. The downside of using a frontend cache though is that they don't respond well to updating content and will often keep an old version of a page cached after it has been updated.
|
||||
Many websites use a frontend cache such as [Varnish](https://varnish-cache.org/), [Squid](http://www.squid-cache.org/), [Cloudflare](https://www.cloudflare.com/) or [CloudFront](https://aws.amazon.com/cloudfront/) to support high volumes of traffic with excellent response times. The downside of using a frontend cache though is that they don't respond well to updating content and will often keep an old version of a page cached after it has been updated.
|
||||
|
||||
Wagtail supports being [integrated](frontend_cache_purging) with many CDNs, so it can inform them when a page changes, so the cache can be cleared immediately and users see the changes sooner.
|
||||
|
||||
If you have multiple frontends configured (eg Cloudflare for one site, CloudFront for another), it's recommended to set the [`HOSTNAMES`](frontendcache_multiple_backends) key to the list of hostnames the backend can purge, to prevent unnecessary extra purge requests.
|
||||
|
||||
(performance_page_urls)=
|
||||
|
||||
## Page URLs
|
||||
|
||||
To fully resolve the URL of a page, Wagtail requires information from a few different sources.
|
||||
|
@ -90,14 +92,6 @@ Wagtail is tested on PostgreSQL, SQLite, and MySQL. It may work on some third-pa
|
|||
|
||||
We recommend PostgreSQL for production use, however, the choice of database ultimately depends on a combination of factors, including personal preference, team expertise, and specific project requirements. The most important aspect is to ensure that your selected database can meet the performance and scalability requirements of your project.
|
||||
|
||||
(caching_proxy)=
|
||||
|
||||
## Caching proxy
|
||||
|
||||
To support high volumes of traffic with excellent response times, we recommend a caching proxy. Both [Varnish](https://varnish-cache.org/) and [Squid](http://www.squid-cache.org/) have been tested in production. Hosted proxies like [Cloudflare](https://www.cloudflare.com/) should also work well.
|
||||
|
||||
Wagtail supports automatic cache invalidation for Varnish/Squid. See [](frontend_cache_purging) for more information.
|
||||
|
||||
### Image attributes
|
||||
|
||||
For some images, it may be beneficial to lazy load images, so the rest of the page can continue to load. It can be configured site-wide [](adding_default_attributes_to_images) or per-image [](image_tag_alt). For more details you can read about the [`loading='lazy'` attribute](https://developer.mozilla.org/en-US/docs/Web/Performance/Lazy_loading#images_and_iframes) and the [`'decoding='async'` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-decoding) or this [web.dev article on lazy loading images](https://web.dev/lazy-loading-images/).
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
Wagtail provides several generic views for handling common tasks such as creating / editing model instances and chooser modals. For convenience, these views are bundled in [viewsets](viewsets_reference).
|
||||
|
||||
(modelviewset)=
|
||||
|
||||
## ModelViewSet
|
||||
|
||||
The {class}`~wagtail.admin.viewsets.model.ModelViewSet` class provides the views for listing, creating, editing, and deleting model instances. For example, if we have the following model:
|
||||
|
|
|
@ -164,3 +164,13 @@ Options:
|
|||
|
||||
- `--purge-only` :
|
||||
This argument will purge all image renditions without regenerating them. They will be regenerated when next requested.
|
||||
|
||||
(convert_mariadb_uuids)=
|
||||
|
||||
## convert_mariadb_uuids
|
||||
|
||||
```sh
|
||||
./manage.py convert_mariadb_uuids
|
||||
```
|
||||
|
||||
For sites using MariaDB, this command must be run once when upgrading to Django 5.0 and MariaDB 10.7 from any earlier version of Django or MariaDB. This is necessary because Django 5.0 introduces support for MariaDB's native UUID type, breaking backwards compatibility with `CHAR`-based UUIDs used in earlier versions of Django and MariaDB. New sites created under Django 5.0+ and MariaDB 10.7+ are unaffected.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Wagtail 5.2.5 release notes - IN DEVELOPMENT
|
||||
# Wagtail 5.2.5 release notes
|
||||
|
||||
_Unreleased_
|
||||
_May 1, 2024_
|
||||
|
||||
```{contents}
|
||||
---
|
||||
|
@ -15,3 +15,16 @@ depth: 1
|
|||
|
||||
* Respect `WAGTAIL_ALLOW_UNICODE_SLUGS` setting when auto-generating slugs (LB (Ben) Johnston)
|
||||
* Use correct URL when redirecting back to page search results after an AJAX search (Sage Abdullah)
|
||||
* Provide [`convert_mariadb_uuids`](convert_mariadb_uuids) management command to assist with upgrading to Django 5.0+ on MariaDB (Matt Westcott)
|
||||
|
||||
## Upgrade considerations
|
||||
|
||||
### Changes to UUID fields on MariaDB when upgrading to Django 5.0
|
||||
|
||||
Django 5.0 introduces support for MariaDB's native UUID type on MariaDB 10.7 and above. This breaks backwards compatibility with `CHAR`-based UUIDs created on earlier versions of Django and MariaDB, and so upgrading a site to Django 5.0+ and MariaDB 10.7+ is liable to result in errors such as `Data too long for column 'translation_key' at row 1` or `Data too long for column 'uuid' at row 1` when creating or editing pages. To fix this, it is necessary to run the [`convert_mariadb_uuids`](convert_mariadb_uuids) management command (available as of Wagtail 5.2.5) after upgrading:
|
||||
|
||||
```sh
|
||||
./manage.py convert_mariadb_uuids
|
||||
```
|
||||
|
||||
This will convert all existing UUID fields used by Wagtail to the new format. New sites created under Django 5.0+ and MariaDB 10.7+ are unaffected.
|
||||
|
|
|
@ -219,6 +219,18 @@ As part of our [adoption of Stimulus](https://github.com/wagtail/rfcs/blob/main/
|
|||
* Add better deprecation warnings to the `search.Query` & `search.QueryDailyHits` model, move final set of templates from the admin search module to the search promotions contrib module (LB (Ben) Johnston)
|
||||
|
||||
|
||||
## Upgrade considerations - changes affecting all projects
|
||||
|
||||
### Changes to UUID fields on MariaDB when upgrading to Django 5.0
|
||||
|
||||
Django 5.0 introduces support for MariaDB's native UUID type on MariaDB 10.7 and above. This breaks backwards compatibility with `CHAR`-based UUIDs created on earlier versions of Django and MariaDB, and so upgrading a site to Django 5.0+ and MariaDB 10.7+ is liable to result in errors such as `Data too long for column 'translation_key' at row 1` or `Data too long for column 'uuid' at row 1` when creating or editing pages. To fix this, it is necessary to run the [`convert_mariadb_uuids`](convert_mariadb_uuids) management command (available as of Wagtail 5.2.5) after upgrading:
|
||||
|
||||
```sh
|
||||
./manage.py convert_mariadb_uuids
|
||||
```
|
||||
|
||||
This will convert all existing UUID fields used by Wagtail to the new format. New sites created under Django 5.0+ and MariaDB 10.7+ are unaffected.
|
||||
|
||||
## Upgrade considerations - deprecation of old functionality
|
||||
|
||||
### Legacy moderation system is deprecated
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Wagtail 6.0.3 release notes - IN DEVELOPMENT
|
||||
# Wagtail 6.0.3 release notes
|
||||
|
||||
_Unreleased_
|
||||
_May 1, 2024_
|
||||
|
||||
```{contents}
|
||||
---
|
||||
|
@ -11,7 +11,30 @@ depth: 1
|
|||
|
||||
## What's new
|
||||
|
||||
### CVE-2024-32882: Permission check bypass when editing a model with per-field restrictions through `wagtail.contrib.settings` or `ModelViewSet`
|
||||
|
||||
This release addresses a permission vulnerability in the Wagtail admin interface. If a model has been made available for editing through the [`wagtail.contrib.settings`](/reference/contrib/settings) module or [ModelViewSet](modelviewset), and the permission argument on FieldPanel has been used to further restrict access to one or more fields of the model, a user with edit permission over the model but not the specific field can craft an HTTP POST request that bypasses the permission check on the individual field, allowing them to update its value.
|
||||
|
||||
The vulnerability is not exploitable by an ordinary site visitor without access to the Wagtail admin, or by a user who has not been granted edit access to the model in question. The editing interfaces for pages and snippets are also unaffected.
|
||||
|
||||
Many thanks to Ben Morse and Joshua Munn for reporting this issue, and Jake Howard and Sage Abdullah for the fix. For further details, please see [the CVE-2024-32882 security advisory](https://github.com/wagtail/wagtail/security/advisories/GHSA-w2v8-php4-p8hc).
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Respect `WAGTAIL_ALLOW_UNICODE_SLUGS` setting when auto-generating slugs (LB (Ben) Johnston)
|
||||
* Use correct URL when redirecting back to page search results after an AJAX search (Sage Abdullah)
|
||||
* Reinstate missing static files in style guide (Sage Abdullah)
|
||||
* Provide [`convert_mariadb_uuids`](convert_mariadb_uuids) management command to assist with upgrading to Django 5.0+ on MariaDB (Matt Westcott)
|
||||
* Fix generic CopyView for models with primary keys that need to be quoted (Sage Abdullah)
|
||||
|
||||
## Upgrade considerations
|
||||
|
||||
### Changes to UUID fields on MariaDB when upgrading to Django 5.0
|
||||
|
||||
Django 5.0 introduces support for MariaDB's native UUID type on MariaDB 10.7 and above. This breaks backwards compatibility with `CHAR`-based UUIDs created on earlier versions of Django and MariaDB, and so upgrading a site to Django 5.0+ and MariaDB 10.7+ is liable to result in errors such as `Data too long for column 'translation_key' at row 1` or `Data too long for column 'uuid' at row 1` when creating or editing pages. To fix this, it is necessary to run the [`convert_mariadb_uuids`](convert_mariadb_uuids) management command (available as of Wagtail 6.0.3) after upgrading:
|
||||
|
||||
```sh
|
||||
./manage.py convert_mariadb_uuids
|
||||
```
|
||||
|
||||
This will convert all existing UUID fields used by Wagtail to the new format. New sites created under Django 5.0+ and MariaDB 10.7+ are unaffected.
|
||||
|
|
|
@ -283,6 +283,16 @@ The `use_json_field` argument to `StreamField` is no longer required, and can be
|
|||
|
||||
## Upgrade considerations - changes affecting all projects
|
||||
|
||||
### Changes to UUID fields on MariaDB when upgrading to Django 5.0
|
||||
|
||||
Django 5.0 introduces support for MariaDB's native UUID type on MariaDB 10.7 and above. This breaks backwards compatibility with `CHAR`-based UUIDs created on earlier versions of Django and MariaDB, and so upgrading a site to Django 5.0+ and MariaDB 10.7+ is liable to result in errors such as `Data too long for column 'translation_key' at row 1` or `Data too long for column 'uuid' at row 1` when creating or editing pages. To fix this, it is necessary to run the [`convert_mariadb_uuids`](convert_mariadb_uuids) management command (available as of Wagtail 6.0.3) after upgrading:
|
||||
|
||||
```sh
|
||||
./manage.py convert_mariadb_uuids
|
||||
```
|
||||
|
||||
This will convert all existing UUID fields used by Wagtail to the new format. New sites created under Django 5.0+ and MariaDB 10.7+ are unaffected.
|
||||
|
||||
### `SnippetViewSet` & `ModelViewSet` copy view enabled by default
|
||||
|
||||
The newly introduced copy view will be enabled by default for all `ModelViewSet` and `SnippetViewSet` classes.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Wagtail 6.1 release notes - IN DEVELOPMENT
|
||||
# Wagtail 6.1 release notes
|
||||
|
||||
_Unreleased_
|
||||
_May 1, 2024_
|
||||
|
||||
```{contents}
|
||||
---
|
||||
|
@ -24,8 +24,12 @@ Continuing work on the Universal Listings project, this release rolls out univer
|
|||
* Groups
|
||||
* Users
|
||||
* Workflow and task views
|
||||
* Search promotions index views
|
||||
* Redirects index
|
||||
|
||||
Universal listing components like header buttons have also been tweaked to improve usability. This feature was developed by Ben Enright and Sage Abdullah.
|
||||
Universal listing components like header buttons have also been tweaked to improve usability, and the `PageListingViewSet` now includes `ChooseParentView` to allow creating pages from custom page listings.
|
||||
|
||||
Thank you to everyone who worked on these features: Ben Enright, Sage Abdullah, Rohit Sharma, Storm Heg, Temidayo Azeez, and Abdelrahman Hamada.
|
||||
|
||||
### Information-dense admin interface
|
||||
|
||||
|
@ -44,21 +48,38 @@ A new viewset class `PageListingViewSet` has been introduced, allowing developer
|
|||
|
||||
A new dialog is available from the help menu, providing an overview of keyboard shortcuts available in the Wagtail admin. This feature was developed by Karthik Ayangar and Rohit Sharma.
|
||||
|
||||
### Better guidance for password-protected content
|
||||
|
||||
Wagtail now includes extra guidance in its [private pages](private_pages) and [private collections (documents)](private_collections) forms, to warn users about the pitfalls of the "shared password" option.
|
||||
For projects with higher security requirements, it's now possible to disable the shared password option entirely.
|
||||
Thank you to Rohit Sharma, Salvo Polizzi, and Jake Howard for implementing those changes.
|
||||
|
||||
### Favicon images generation
|
||||
|
||||
For sites managing favicons via the CMS, Wagtail now supports [`.ico` favicon generation](favicon_generation), with `format-ico`:
|
||||
|
||||
```html+django
|
||||
<link rel="icon" href="{% image favicon_image format-ico %}" />
|
||||
```
|
||||
|
||||
This feature was developed by Jake Howard.
|
||||
|
||||
### CVE-2024-32882: Permission check bypass when editing a model with per-field restrictions through `wagtail.contrib.settings` or `ModelViewSet`
|
||||
|
||||
This release addresses a permission vulnerability in the Wagtail admin interface. If a model has been made available for editing through the [`wagtail.contrib.settings`](/reference/contrib/settings) module or [ModelViewSet](modelviewset), and the permission argument on FieldPanel has been used to further restrict access to one or more fields of the model, a user with edit permission over the model but not the specific field can craft an HTTP POST request that bypasses the permission check on the individual field, allowing them to update its value.
|
||||
|
||||
The vulnerability is not exploitable by an ordinary site visitor without access to the Wagtail admin, or by a user who has not been granted edit access to the model in question. The editing interfaces for pages and snippets are also unaffected.
|
||||
|
||||
Many thanks to Ben Morse and Joshua Munn for reporting this issue, and Jake Howard and Sage Abdullah for the fix. For further details, please see [the CVE-2024-32882 security advisory](https://github.com/wagtail/wagtail/security/advisories/GHSA-w2v8-php4-p8hc).
|
||||
|
||||
### Other features
|
||||
|
||||
* Refine wording of page & collection privacy using password is a shared password and should not be used for secure content (Rohit Sharma, Jake Howard)
|
||||
* Add `RelatedObjectsColumn` to the table UI framework (Matt Westcott)
|
||||
* 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)
|
||||
* Allow custom permissions with the same prefix as built-in permissions (Sage Abdullah)
|
||||
* Allow displaying permissions linked to the Admin model's content type (Sage Abdullah)
|
||||
* Add support for Draftail's JavaScript to use chooserUrls provided by entity options & for the Draftail widget to encode lazy URLs/ translations (Elhussein Almasri)
|
||||
* Reimplement search promotions `IndexView` using the `generic.IndexView` (Rohit Sharma, Sage Abdullah, Storm Heg)
|
||||
* Reimplement redirects `IndexView` using the `generic.IndexView` (Rohit Sharma, Sage Abdullah, Temidayo Azeez)
|
||||
* Add `ChooseParentView` to `PageListingViewSet` to allow creating pages from custom page listings (Abdelrahman Hamada, Sage Abdullah)
|
||||
* Added `AbstractGroupApprovalTask` to simplify [customizing behavior of custom `Task` models](../extending/custom_tasks) (John-Scott Atlakson)
|
||||
* Add ability to bulk toggle permissions in the user group editing view, including shift+click for multiple selections (LB (Ben) Johnston, Kalob Taulien)
|
||||
* Update the minimum version of `djangorestframework` to 3.15.1 (Sage Abdullah)
|
||||
|
@ -97,6 +118,8 @@ A new dialog is available from the help menu, providing an overview of keyboard
|
|||
* Improve exception handling when generating image renditions concurrently (Andy Babic)
|
||||
* Respect `WAGTAIL_ALLOW_UNICODE_SLUGS` setting when auto-generating slugs (LB (Ben) Johnston)
|
||||
* Use correct URL when redirecting back to page search results after an AJAX search (Sage Abdullah)
|
||||
* Reinstate missing static files in style guide (Sage Abdullah)
|
||||
* Provide [`convert_mariadb_uuids`](convert_mariadb_uuids) management command to assist with upgrading to Django 5.0+ on MariaDB (Matt Westcott)
|
||||
|
||||
|
||||
### Documentation
|
||||
|
|
|
@ -16,19 +16,22 @@ depth: 1
|
|||
|
||||
* Optimize and consolidate redirects report view into the index view (Jake Howard, Dan Braghis)
|
||||
* Support a [`HOSTNAMES` parameter on `WAGTAILFRONTENDCACHE`](frontendcache_multiple_backends) to define which hostnames a backend should respond to (Jake Howard, sponsored by Oxfam America)
|
||||
* Refactor redirects edit view to use the generic `EditView` and breadcrumbs (Rohit Sharma)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Make `WAGTAILIMAGES_CHOOSER_PAGE_SIZE` setting functional again (Rohit Sharma)
|
||||
* Enable `richtext` template tag to convert lazy translation values (Benjamin Bach)
|
||||
|
||||
|
||||
### Documentation
|
||||
|
||||
* ...
|
||||
* Remove duplicate section on frontend caching proxies from performance page (Jake Howard)
|
||||
|
||||
|
||||
### Maintenance
|
||||
|
||||
* ...
|
||||
* Use `DjangoJSONEncoder` instead of custom `LazyStringEncoder` to serialize Draftail config (Sage Abdullah)
|
||||
|
||||
|
||||
## Upgrade considerations - changes affecting all projects
|
||||
|
|
|
@ -442,6 +442,8 @@ You can encode the image into lossless AVIF or WebP format by using `format-avif
|
|||
{% image page.photo width-400 format-webp-lossless %}
|
||||
```
|
||||
|
||||
(favicon_generation)=
|
||||
|
||||
### Favicon generation
|
||||
|
||||
You can save images as a `.ico` file using `format-ico`, which is especially useful when managing a site's favicon through the Admin.
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"version": "6.0.2",
|
||||
"url": "https://docs.wagtail.org/en/stable/releases/6.0.2.html",
|
||||
"minorUrl": "https://docs.wagtail.org/en/stable/releases/6.0.html",
|
||||
"version": "6.1",
|
||||
"url": "https://docs.wagtail.org/en/stable/releases/6.1.html",
|
||||
"minorUrl": "https://docs.wagtail.org/en/stable/releases/6.1.html",
|
||||
"lts": {
|
||||
"version": "5.2.4",
|
||||
"url": "https://docs.wagtail.org/en/stable/releases/5.2.4.html",
|
||||
"version": "5.2.5",
|
||||
"url": "https://docs.wagtail.org/en/stable/releases/5.2.5.html",
|
||||
"minorUrl": "https://docs.wagtail.org/en/stable/releases/5.2.html"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-04-18 17:28+0100\n"
|
||||
"POT-Creation-Date: 2024-05-02 10:04+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
@ -50,7 +50,7 @@ msgstr ""
|
|||
|
||||
#: action_menu.py:196 templates/wagtailadmin/generic/confirm_unpublish.html:5
|
||||
#: templates/wagtailadmin/pages/bulk_actions/confirm_bulk_unpublish.html:7
|
||||
#: views/pages/bulk_actions/unpublish.py:8 wagtail_hooks.py:341
|
||||
#: views/pages/bulk_actions/unpublish.py:8 wagtail_hooks.py:342
|
||||
msgid "Unpublish"
|
||||
msgstr ""
|
||||
|
||||
|
@ -199,7 +199,7 @@ msgstr ""
|
|||
msgid "You cannot have multiple permission records for the same collection."
|
||||
msgstr ""
|
||||
|
||||
#: forms/collections.py:378 views/generic/models.py:459
|
||||
#: forms/collections.py:378 views/generic/models.py:460
|
||||
msgid "Add"
|
||||
msgstr ""
|
||||
|
||||
|
@ -207,21 +207,20 @@ msgstr ""
|
|||
msgid "Add collections"
|
||||
msgstr ""
|
||||
|
||||
#: forms/collections.py:379 rich_text/editors/draftail/__init__.py:24
|
||||
#: templates/wagtailadmin/generic/inspect.html:31
|
||||
#: forms/collections.py:379 templates/wagtailadmin/generic/inspect.html:31
|
||||
#: templates/wagtailadmin/home/locked_pages.html:46
|
||||
#: templates/wagtailadmin/home/recent_edits.html:35
|
||||
#: templates/wagtailadmin/home/user_objects_in_workflow_moderation.html:29
|
||||
#: templates/wagtailadmin/home/workflow_objects_to_moderate.html:29
|
||||
#: templates/wagtailadmin/home/workflow_objects_to_moderate.html:52
|
||||
#: templates/wagtailadmin/shared/workflow_history/index.html:12
|
||||
#: views/generic/history.py:133 views/generic/history.py:269
|
||||
#: views/generic/models.py:384 views/generic/models.py:589
|
||||
#: views/generic/models.py:847 views/generic/models.py:1152
|
||||
#: views/generic/models.py:1294 views/generic/models.py:1403
|
||||
#: views/generic/history.py:133 views/generic/history.py:268
|
||||
#: views/generic/models.py:385 views/generic/models.py:590
|
||||
#: views/generic/models.py:873 views/generic/models.py:1178
|
||||
#: views/generic/models.py:1320 views/generic/models.py:1429
|
||||
#: views/generic/usage.py:88 views/pages/create.py:172 views/pages/edit.py:291
|
||||
#: views/pages/move.py:116 views/workflows.py:236 views/workflows.py:352
|
||||
#: viewsets/chooser.py:30 wagtail_hooks.py:244
|
||||
#: viewsets/chooser.py:30 wagtail_hooks.py:245
|
||||
msgid "Edit"
|
||||
msgstr ""
|
||||
|
||||
|
@ -236,9 +235,9 @@ msgstr ""
|
|||
#: templates/wagtailadmin/panels/inline_panel_child.html:11
|
||||
#: templates/wagtailadmin/permissions/includes/collection_member_permissions_form.html:16
|
||||
#: templates/wagtailadmin/workflows/includes/workflow_pages_form.html:8
|
||||
#: views/generic/models.py:429 views/generic/models.py:699
|
||||
#: views/generic/models.py:914 views/pages/bulk_actions/delete.py:8
|
||||
#: wagtail_hooks.py:311
|
||||
#: views/generic/models.py:430 views/generic/models.py:718
|
||||
#: views/generic/models.py:940 views/pages/bulk_actions/delete.py:8
|
||||
#: wagtail_hooks.py:312
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
|
@ -538,7 +537,7 @@ msgstr ""
|
|||
msgid "Promote"
|
||||
msgstr ""
|
||||
|
||||
#: panels/page_utils.py:68 wagtail_hooks.py:125
|
||||
#: panels/page_utils.py:68 wagtail_hooks.py:126
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
|
@ -835,7 +834,7 @@ msgstr ""
|
|||
#: templates/wagtailadmin/page_privacy/set_privacy.html:12
|
||||
#: templates/wagtailadmin/workflows/edit.html:60
|
||||
#: templates/wagtailadmin/workflows/edit_task.html:44
|
||||
#: views/generic/models.py:702
|
||||
#: views/generic/models.py:721
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1085,7 +1084,7 @@ msgstr ""
|
|||
#: templates/wagtailadmin/home/recent_edits.html:42
|
||||
#: templates/wagtailadmin/shared/page_status_tag_new.html:50
|
||||
#: templates/wagtailadmin/shared/side_panels/includes/status/workflow.html:18
|
||||
#: views/generic/models.py:1186
|
||||
#: views/generic/models.py:1212
|
||||
msgid "Live"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1108,7 +1107,7 @@ msgid "Status"
|
|||
msgstr ""
|
||||
|
||||
#: templates/wagtailadmin/home/recent_edits.html:14 views/generic/history.py:58
|
||||
#: views/generic/history.py:226 views/reports/audit_logging.py:66
|
||||
#: views/generic/history.py:225 views/reports/audit_logging.py:66
|
||||
msgid "Date"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1674,7 +1673,7 @@ msgstr[1] ""
|
|||
#: templates/wagtailadmin/pages/bulk_actions/confirm_bulk_move.html:8
|
||||
#: templates/wagtailadmin/pages/confirm_move.html:5
|
||||
#: templates/wagtailadmin/pages/move_choose_destination.html:5
|
||||
#: views/pages/bulk_actions/move.py:31 wagtail_hooks.py:289
|
||||
#: views/pages/bulk_actions/move.py:31 wagtail_hooks.py:290
|
||||
msgid "Move"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2016,8 +2015,8 @@ msgid "Copy %(title)s"
|
|||
msgstr ""
|
||||
|
||||
#: templates/wagtailadmin/pages/copy.html:5
|
||||
#: templatetags/wagtailadmin_tags.py:1328 views/generic/models.py:398
|
||||
#: wagtail_hooks.py:300
|
||||
#: templatetags/wagtailadmin_tags.py:1328 views/generic/models.py:399
|
||||
#: wagtail_hooks.py:301
|
||||
msgid "Copy"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2099,7 +2098,7 @@ msgstr ""
|
|||
|
||||
#: templates/wagtailadmin/pages/listing/_navigation_explore.html:17
|
||||
#: templates/wagtailadmin/pages/listing/_navigation_explore.html:18
|
||||
#: wagtail_hooks.py:280
|
||||
#: wagtail_hooks.py:281
|
||||
#, python-format
|
||||
msgid "Add a child page to '%(title)s'"
|
||||
msgstr ""
|
||||
|
@ -2197,7 +2196,7 @@ msgid "Confirm"
|
|||
msgstr ""
|
||||
|
||||
#: templates/wagtailadmin/pages/page_listing_header.html:12
|
||||
#: wagtail_hooks.py:278
|
||||
#: wagtail_hooks.py:279
|
||||
msgid "Add child page"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2322,7 +2321,7 @@ msgid "App"
|
|||
msgstr ""
|
||||
|
||||
#: templates/wagtailadmin/reports/listing/_list_page_types_usage.html:19
|
||||
#: wagtail_hooks.py:102 wagtail_hooks.py:143
|
||||
#: wagtail_hooks.py:103 wagtail_hooks.py:144
|
||||
msgid "Pages"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2368,7 +2367,7 @@ msgstr ""
|
|||
|
||||
#: templates/wagtailadmin/reports/site_history.html:10
|
||||
#: templates/wagtailadmin/workflows/task_chooser/includes/results.html:19
|
||||
#: views/account.py:152 views/collections.py:27 views/generic/models.py:254
|
||||
#: views/account.py:152 views/collections.py:27 views/generic/models.py:255
|
||||
#: views/generic/usage.py:103 views/reports/audit_logging.py:68
|
||||
#: views/reports/audit_logging.py:114 views/workflows.py:125
|
||||
#: views/workflows.py:520
|
||||
|
@ -2376,7 +2375,7 @@ msgid "Name"
|
|||
msgstr ""
|
||||
|
||||
#: templates/wagtailadmin/reports/site_history.html:16
|
||||
#: views/generic/history.py:48 views/generic/history.py:214
|
||||
#: views/generic/history.py:48 views/generic/history.py:213
|
||||
#: views/reports/audit_logging.py:57
|
||||
msgid "Action"
|
||||
msgstr ""
|
||||
|
@ -2502,8 +2501,8 @@ msgstr ""
|
|||
|
||||
#: templates/wagtailadmin/shared/headers/_history_icon_link.html:5
|
||||
#: templates/wagtailadmin/shared/headers/_history_icon_link.html:7
|
||||
#: views/generic/history.py:194 views/generic/history.py:259
|
||||
#: views/generic/models.py:1153 wagtail_hooks.py:352
|
||||
#: views/generic/history.py:194 views/generic/history.py:258
|
||||
#: views/generic/models.py:1179 wagtail_hooks.py:353
|
||||
msgid "History"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2949,7 +2948,7 @@ msgstr ""
|
|||
#: templates/wagtailadmin/workflows/create.html:36
|
||||
#: templates/wagtailadmin/workflows/create_task.html:32
|
||||
#: templates/wagtailadmin/workflows/task_chooser/includes/create_form.html:30
|
||||
#: views/generic/chooser.py:302 views/generic/models.py:508
|
||||
#: views/generic/chooser.py:302 views/generic/models.py:509
|
||||
#: viewsets/chooser.py:82
|
||||
msgid "Create"
|
||||
msgstr ""
|
||||
|
@ -3050,7 +3049,7 @@ msgid "Choose a task"
|
|||
msgstr ""
|
||||
|
||||
#: templates/wagtailadmin/workflows/task_chooser/chooser.html:8
|
||||
#: views/generic/models.py:502 views/pages/create.py:67
|
||||
#: views/generic/models.py:503 views/pages/create.py:67
|
||||
msgid "New"
|
||||
msgstr ""
|
||||
|
||||
|
@ -3241,11 +3240,11 @@ msgstr ""
|
|||
msgid "Text formatting"
|
||||
msgstr ""
|
||||
|
||||
#: templatetags/wagtailadmin_tags.py:1351 wagtail_hooks.py:693
|
||||
#: templatetags/wagtailadmin_tags.py:1351 wagtail_hooks.py:694
|
||||
msgid "Bold"
|
||||
msgstr ""
|
||||
|
||||
#: templatetags/wagtailadmin_tags.py:1352 wagtail_hooks.py:715
|
||||
#: templatetags/wagtailadmin_tags.py:1352 wagtail_hooks.py:716
|
||||
msgid "Italic"
|
||||
msgstr ""
|
||||
|
||||
|
@ -3261,11 +3260,11 @@ msgstr ""
|
|||
msgid "Strike-through"
|
||||
msgstr ""
|
||||
|
||||
#: templatetags/wagtailadmin_tags.py:1356 wagtail_hooks.py:785
|
||||
#: templatetags/wagtailadmin_tags.py:1356 wagtail_hooks.py:786
|
||||
msgid "Superscript"
|
||||
msgstr ""
|
||||
|
||||
#: templatetags/wagtailadmin_tags.py:1357 wagtail_hooks.py:806
|
||||
#: templatetags/wagtailadmin_tags.py:1357 wagtail_hooks.py:807
|
||||
msgid "Subscript"
|
||||
msgstr ""
|
||||
|
||||
|
@ -3391,7 +3390,7 @@ msgstr ""
|
|||
msgid "You have been successfully logged out."
|
||||
msgstr ""
|
||||
|
||||
#: views/collections.py:21 wagtail_hooks.py:174
|
||||
#: views/collections.py:21 wagtail_hooks.py:175
|
||||
msgid "Collections"
|
||||
msgstr ""
|
||||
|
||||
|
@ -3455,7 +3454,7 @@ msgstr ""
|
|||
msgid "Cancel scheduled publish"
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/history.py:356
|
||||
#: views/generic/history.py:355
|
||||
msgid "Workflow progress"
|
||||
msgstr ""
|
||||
|
||||
|
@ -3469,12 +3468,12 @@ msgstr ""
|
|||
msgid "The %(model_name)s could not be saved as it is locked"
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/mixins.py:418 views/generic/models.py:700
|
||||
#: views/generic/mixins.py:418 views/generic/models.py:719
|
||||
#, python-format
|
||||
msgid "%(model_name)s '%(object)s' updated."
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/mixins.py:420 views/generic/models.py:504
|
||||
#: views/generic/mixins.py:420 views/generic/models.py:505
|
||||
#, python-format
|
||||
msgid "%(model_name)s '%(object)s' created."
|
||||
msgstr ""
|
||||
|
@ -3553,97 +3552,97 @@ msgid ""
|
|||
"for publishing."
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/models.py:284
|
||||
#: views/generic/models.py:285
|
||||
#, python-format
|
||||
msgid "%(related_model_name)s %(field_label)s"
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/models.py:388 wagtail_hooks.py:246
|
||||
#: views/generic/models.py:389 wagtail_hooks.py:247
|
||||
#, python-format
|
||||
msgid "Edit '%(title)s'"
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/models.py:402
|
||||
#: views/generic/models.py:403
|
||||
#, python-format
|
||||
msgid "Copy '%(title)s'"
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/models.py:411 views/generic/models.py:1056
|
||||
#: views/generic/models.py:412 views/generic/models.py:1082
|
||||
msgid "Inspect"
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/models.py:415
|
||||
#: views/generic/models.py:416
|
||||
#, python-format
|
||||
msgid "Inspect '%(title)s'"
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/models.py:433
|
||||
#: views/generic/models.py:434
|
||||
#, python-format
|
||||
msgid "Delete '%(title)s'"
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/models.py:447 wagtail_hooks.py:236
|
||||
#: views/generic/models.py:448 wagtail_hooks.py:237
|
||||
#, python-format
|
||||
msgid "More options for '%(title)s'"
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/models.py:457
|
||||
#: views/generic/models.py:458
|
||||
#, python-format
|
||||
msgid "Add %(model_name)s"
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/models.py:506
|
||||
#: views/generic/models.py:507
|
||||
#, python-format
|
||||
msgid "The %(model_name)s could not be created due to errors."
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/models.py:543 views/workflows.py:644
|
||||
#: views/generic/models.py:544 views/workflows.py:644
|
||||
#, python-format
|
||||
msgid "New: %(model_name)s"
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/models.py:695
|
||||
#: views/generic/models.py:714
|
||||
msgid "Editing"
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/models.py:701
|
||||
#: views/generic/models.py:720
|
||||
#, python-format
|
||||
msgid "The %(model_name)s could not be saved due to errors."
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/models.py:915
|
||||
#: views/generic/models.py:941
|
||||
#, python-format
|
||||
msgid "%(model_name)s '%(object)s' deleted."
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/models.py:974
|
||||
#: views/generic/models.py:1000
|
||||
#, python-format
|
||||
msgid "Are you sure you want to delete this %(model_name)s?"
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/models.py:1019
|
||||
#: views/generic/models.py:1045
|
||||
msgid "Inspecting"
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/models.py:1191
|
||||
#: views/generic/models.py:1217
|
||||
msgid "Earliest"
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/models.py:1194
|
||||
#: views/generic/models.py:1220
|
||||
msgid "Latest"
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/models.py:1257
|
||||
#: views/generic/models.py:1283
|
||||
#, python-format
|
||||
msgid "'%(object)s' unpublished."
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/models.py:1364
|
||||
#: views/generic/models.py:1390
|
||||
#, python-format
|
||||
msgid "Version %(revision_id)s of \"%(object)s\" unscheduled."
|
||||
msgstr ""
|
||||
|
||||
#: views/generic/models.py:1420
|
||||
#: views/generic/models.py:1446
|
||||
#, python-format
|
||||
msgid "revision %(revision_id)s of \"%(object)s\""
|
||||
msgstr ""
|
||||
|
@ -3819,11 +3818,11 @@ msgstr ""
|
|||
msgid "Page '%(page_title)s' copied."
|
||||
msgstr ""
|
||||
|
||||
#: views/pages/create.py:178 views/pages/edit.py:297 wagtail_hooks.py:255
|
||||
#: views/pages/create.py:178 views/pages/edit.py:297 wagtail_hooks.py:256
|
||||
msgid "View draft"
|
||||
msgstr ""
|
||||
|
||||
#: views/pages/create.py:183 views/pages/edit.py:302 wagtail_hooks.py:267
|
||||
#: views/pages/create.py:183 views/pages/edit.py:302 wagtail_hooks.py:268
|
||||
msgid "View live"
|
||||
msgstr ""
|
||||
|
||||
|
@ -4030,7 +4029,7 @@ msgstr ""
|
|||
msgid "Last published before"
|
||||
msgstr ""
|
||||
|
||||
#: views/reports/aging_pages.py:33 wagtail_hooks.py:946
|
||||
#: views/reports/aging_pages.py:33 wagtail_hooks.py:947
|
||||
msgid "Aging pages"
|
||||
msgstr ""
|
||||
|
||||
|
@ -4042,7 +4041,7 @@ msgstr ""
|
|||
msgid "Hide commenting actions"
|
||||
msgstr ""
|
||||
|
||||
#: views/reports/audit_logging.py:108 wagtail_hooks.py:935
|
||||
#: views/reports/audit_logging.py:108 wagtail_hooks.py:936
|
||||
msgid "Site history"
|
||||
msgstr ""
|
||||
|
||||
|
@ -4058,11 +4057,11 @@ msgstr ""
|
|||
msgid "Date/Time"
|
||||
msgstr ""
|
||||
|
||||
#: views/reports/locked_pages.py:38 wagtail_hooks.py:902
|
||||
#: views/reports/locked_pages.py:38 wagtail_hooks.py:903
|
||||
msgid "Locked pages"
|
||||
msgstr ""
|
||||
|
||||
#: views/reports/page_types_usage.py:100 wagtail_hooks.py:957
|
||||
#: views/reports/page_types_usage.py:100 wagtail_hooks.py:958
|
||||
msgid "Page types usage"
|
||||
msgstr ""
|
||||
|
||||
|
@ -4075,7 +4074,7 @@ msgid "Awaiting my review"
|
|||
msgstr ""
|
||||
|
||||
#: views/reports/workflows.py:139 views/workflows.py:119 views/workflows.py:588
|
||||
#: wagtail_hooks.py:205 wagtail_hooks.py:913
|
||||
#: wagtail_hooks.py:206 wagtail_hooks.py:914
|
||||
msgid "Workflows"
|
||||
msgstr ""
|
||||
|
||||
|
@ -4091,8 +4090,8 @@ msgstr ""
|
|||
msgid "Page/Snippet Title"
|
||||
msgstr ""
|
||||
|
||||
#: views/reports/workflows.py:211 views/workflows.py:514 wagtail_hooks.py:216
|
||||
#: wagtail_hooks.py:924
|
||||
#: views/reports/workflows.py:211 views/workflows.py:514 wagtail_hooks.py:217
|
||||
#: wagtail_hooks.py:925
|
||||
msgid "Workflow tasks"
|
||||
msgstr ""
|
||||
|
||||
|
@ -4222,98 +4221,98 @@ msgstr ""
|
|||
msgid "Choose another"
|
||||
msgstr ""
|
||||
|
||||
#: wagtail_hooks.py:257
|
||||
#: wagtail_hooks.py:258
|
||||
#, python-format
|
||||
msgid "Preview draft version of '%(title)s'"
|
||||
msgstr ""
|
||||
|
||||
#: wagtail_hooks.py:269
|
||||
#: wagtail_hooks.py:270
|
||||
#, python-format
|
||||
msgid "View live version of '%(title)s'"
|
||||
msgstr ""
|
||||
|
||||
#: wagtail_hooks.py:291
|
||||
#: wagtail_hooks.py:292
|
||||
#, python-format
|
||||
msgid "Move page '%(title)s'"
|
||||
msgstr ""
|
||||
|
||||
#: wagtail_hooks.py:302
|
||||
#: wagtail_hooks.py:303
|
||||
#, python-format
|
||||
msgid "Copy page '%(title)s'"
|
||||
msgstr ""
|
||||
|
||||
#: wagtail_hooks.py:313
|
||||
#: wagtail_hooks.py:314
|
||||
#, python-format
|
||||
msgid "Delete page '%(title)s'"
|
||||
msgstr ""
|
||||
|
||||
#: wagtail_hooks.py:343
|
||||
#: wagtail_hooks.py:344
|
||||
#, python-format
|
||||
msgid "Unpublish page '%(title)s'"
|
||||
msgstr ""
|
||||
|
||||
#: wagtail_hooks.py:354
|
||||
#: wagtail_hooks.py:355
|
||||
#, python-format
|
||||
msgid "View page history for '%(title)s'"
|
||||
msgstr ""
|
||||
|
||||
#: wagtail_hooks.py:363
|
||||
#: wagtail_hooks.py:364
|
||||
msgid "Sort menu order"
|
||||
msgstr ""
|
||||
|
||||
#: wagtail_hooks.py:365
|
||||
#: wagtail_hooks.py:366
|
||||
#, python-format
|
||||
msgid "Change ordering of child pages of '%(title)s'"
|
||||
msgstr ""
|
||||
|
||||
#: wagtail_hooks.py:497 wagtail_hooks.py:518 wagtail_hooks.py:539
|
||||
#: wagtail_hooks.py:560 wagtail_hooks.py:581 wagtail_hooks.py:602
|
||||
#: wagtail_hooks.py:498 wagtail_hooks.py:519 wagtail_hooks.py:540
|
||||
#: wagtail_hooks.py:561 wagtail_hooks.py:582 wagtail_hooks.py:603
|
||||
#, python-format
|
||||
msgid "Heading %(level)d"
|
||||
msgstr ""
|
||||
|
||||
#: wagtail_hooks.py:623
|
||||
#: wagtail_hooks.py:624
|
||||
msgid "Bulleted list"
|
||||
msgstr ""
|
||||
|
||||
#: wagtail_hooks.py:647
|
||||
#: wagtail_hooks.py:648
|
||||
msgid "Numbered list"
|
||||
msgstr ""
|
||||
|
||||
#: wagtail_hooks.py:671
|
||||
#: wagtail_hooks.py:672
|
||||
msgid "Blockquote"
|
||||
msgstr ""
|
||||
|
||||
#: wagtail_hooks.py:738
|
||||
#: wagtail_hooks.py:739
|
||||
msgid "Link"
|
||||
msgstr ""
|
||||
|
||||
#: wagtail_hooks.py:827
|
||||
#: wagtail_hooks.py:828
|
||||
msgid "Strikethrough"
|
||||
msgstr ""
|
||||
|
||||
#: wagtail_hooks.py:848
|
||||
#: wagtail_hooks.py:849
|
||||
msgid "Code"
|
||||
msgstr ""
|
||||
|
||||
#: wagtail_hooks.py:968
|
||||
#: wagtail_hooks.py:969
|
||||
msgid "Reports"
|
||||
msgstr ""
|
||||
|
||||
#: wagtail_hooks.py:980
|
||||
#: wagtail_hooks.py:981
|
||||
#, python-format
|
||||
msgid "What's new in Wagtail %(version)s"
|
||||
msgstr ""
|
||||
|
||||
#: wagtail_hooks.py:992
|
||||
#: wagtail_hooks.py:993
|
||||
msgid "Editor Guide"
|
||||
msgstr ""
|
||||
|
||||
#: wagtail_hooks.py:1009
|
||||
#: wagtail_hooks.py:1010
|
||||
msgid "Shortcuts"
|
||||
msgstr ""
|
||||
|
||||
#: wagtail_hooks.py:1026
|
||||
#: wagtail_hooks.py:1027
|
||||
msgid "Help"
|
||||
msgstr ""
|
||||
|
||||
|
|
Plik binarny nie jest wyświetlany.
|
@ -98,6 +98,9 @@ msgstr "Raccolta"
|
|||
msgid "Tag"
|
||||
msgstr "Tag"
|
||||
|
||||
msgid "Filter by up to ten most popular tags."
|
||||
msgstr "Filtra fino a dieci tag più popolari."
|
||||
|
||||
msgid "Preferred language"
|
||||
msgstr "Lingua preferita"
|
||||
|
||||
|
@ -263,6 +266,20 @@ msgstr "Seleziona un nuovo genitore per questa pagina."
|
|||
msgid "Parent page"
|
||||
msgstr "Pagina padre"
|
||||
|
||||
msgid "The new page will be a child of this given parent page."
|
||||
msgstr "Questa nuova pagina sarà un figlio della pagina padre in questione."
|
||||
|
||||
#, python-format
|
||||
msgid "You do not have permission to create a page under \"%(page_title)s\"."
|
||||
msgstr "Non hai i permessi per creare una pagina sotto \"%(page_title)s\""
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"You cannot create a page of type \"%(page_type)s\" under \"%(page_title)s\"."
|
||||
msgstr ""
|
||||
"Non puoi creare una pagina del tipo \"%(page_type)s\" sotto "
|
||||
"\"%(page_title)s\"."
|
||||
|
||||
msgid "Search…"
|
||||
msgstr "Cerca..."
|
||||
|
||||
|
@ -659,13 +676,12 @@ msgid ""
|
|||
"No collections have been created. Why not <a "
|
||||
"href=\"%(add_collection_url)s\">add one</a>?"
|
||||
msgstr ""
|
||||
"Nessuna raccolta presente. Perché non ne <a "
|
||||
"href=\"%(add_collection_url)s\">aggiungi una</a>?"
|
||||
"Non ci sono raccolte presenti. Perché non ne <a "
|
||||
"href=\"%(add_collection_url)s\">crei una</a>?"
|
||||
|
||||
#, python-format
|
||||
msgid "Sorry, there are no matches for \"<em>%(search_query)s</em>\""
|
||||
msgstr ""
|
||||
"Spiacente, non ci sono corrispondenze per \"<em>%(search_query)s</em>\""
|
||||
msgstr "Non ci sono corrispondenze per \"<em>%(search_query)s</em>\""
|
||||
|
||||
msgid "There are no results."
|
||||
msgstr "Non ci sono risultati."
|
||||
|
@ -1479,6 +1495,25 @@ msgstr[2] ""
|
|||
"L'eliminazione di questa pagina eliminerà anche 1 traduzione e le sue "
|
||||
"%(translation_descendant_count)s pagine figlie tradotte combinate."
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Deleting this page will also delete %(translation_count)s translations and "
|
||||
"their combined %(translation_descendant_count)s translated child page."
|
||||
msgid_plural ""
|
||||
"Deleting this page will also delete %(translation_count)s translations and "
|
||||
"their combined %(translation_descendant_count)s translated child pages."
|
||||
msgstr[0] ""
|
||||
"L'eliminazione di questa pagina eliminerà anche %(translation_count)s "
|
||||
"traduzioni e la loro pagina figlia tradotta %(translation_descendant_count)s."
|
||||
msgstr[1] ""
|
||||
"L'eliminazione di questa pagina eliminerà anche %(translation_count)s "
|
||||
"traduzioni e le relative %(translation_descendant_count)s pagine figlie "
|
||||
"tradotte."
|
||||
msgstr[2] ""
|
||||
"L'eliminazione di questa pagina eliminerà anche %(translation_count)s "
|
||||
"traduzioni e le relative %(translation_descendant_count)s pagine figlie "
|
||||
"tradotte."
|
||||
|
||||
#, python-format
|
||||
msgid "This action will delete total <b>%(total_pages)s</b> pages."
|
||||
msgstr "Questa azione eliminerà un totale di <b>%(total_pages)s</b> pagine."
|
||||
|
@ -1897,6 +1932,18 @@ msgstr "Torna indietro"
|
|||
msgid "History"
|
||||
msgstr "Storico"
|
||||
|
||||
msgid "Keyboard shortcuts"
|
||||
msgstr "Scorciatoie da tastiera"
|
||||
|
||||
msgid "All keyboard shortcuts"
|
||||
msgstr "Tuttle le scorciatoie da dastiera"
|
||||
|
||||
msgid "Section"
|
||||
msgstr "Sezione"
|
||||
|
||||
msgid "Keyboard shortcut"
|
||||
msgstr "Scorciatoia da tastiera"
|
||||
|
||||
msgid "Current page status:"
|
||||
msgstr "Stato attuale della pagina:"
|
||||
|
||||
|
@ -2426,18 +2473,48 @@ msgstr "Inviato a %(task_name)s %(started_at)s"
|
|||
msgid "%(status_display)s %(task_name)s %(started_at)s"
|
||||
msgstr "%(status_display)s %(task_name)s %(started_at)s"
|
||||
|
||||
msgid "Common actions"
|
||||
msgstr "Azioni comuni"
|
||||
|
||||
msgid "Cut"
|
||||
msgstr "Taglia"
|
||||
|
||||
msgid "Paste"
|
||||
msgstr "Incolla"
|
||||
|
||||
msgid "Paste and match style"
|
||||
msgstr "Incolla e mantieni lo stile"
|
||||
|
||||
msgid "Paste without formatting"
|
||||
msgstr "Incolla senza formattazione"
|
||||
|
||||
msgid "Undo"
|
||||
msgstr "Annulla"
|
||||
|
||||
msgid "Redo"
|
||||
msgstr "Ripristina"
|
||||
|
||||
msgid "Save changes"
|
||||
msgstr "Salva modifiche"
|
||||
|
||||
msgid "Text content"
|
||||
msgstr "Contenuto testo"
|
||||
|
||||
msgid "Insert or edit a link"
|
||||
msgstr "Inserisci o modifica un link"
|
||||
|
||||
msgid "Text formatting"
|
||||
msgstr "Formattazione testo"
|
||||
|
||||
msgid "Bold"
|
||||
msgstr "Grassetto"
|
||||
|
||||
msgid "Italic"
|
||||
msgstr "Corsivo"
|
||||
|
||||
msgid "Underline"
|
||||
msgstr "Sottolineatura"
|
||||
|
||||
msgid "Superscript"
|
||||
msgstr "Apice"
|
||||
|
||||
|
@ -2610,6 +2687,10 @@ msgstr "Cancella programma pubblicazione"
|
|||
msgid "Workflow progress"
|
||||
msgstr "Andamento flusso di lavoro"
|
||||
|
||||
#, python-format
|
||||
msgid "%(model_name)s '%(title)s' is now unlocked."
|
||||
msgstr "%(model_name)s '%(title)s' è sbloccata ora."
|
||||
|
||||
#, python-format
|
||||
msgid "%(model_name)s '%(object)s' updated."
|
||||
msgstr "%(model_name)s '%(object)s' aggiornato."
|
||||
|
@ -2622,6 +2703,10 @@ msgstr "%(model_name)s '%(object)s' creato."
|
|||
msgid "Edit '%(title)s'"
|
||||
msgstr "Modifica '%(title)s'"
|
||||
|
||||
#, python-format
|
||||
msgid "Copy '%(title)s'"
|
||||
msgstr "Copia '%(title)s'"
|
||||
|
||||
msgid "Inspect"
|
||||
msgstr "Controlla"
|
||||
|
||||
|
@ -2902,6 +2987,9 @@ msgstr "La pagina non può esser salvata a causa di errori di validazione"
|
|||
msgid "Owner"
|
||||
msgstr "Proprietario"
|
||||
|
||||
msgid "Edited by"
|
||||
msgstr "Modificata da"
|
||||
|
||||
msgid "Site"
|
||||
msgstr "Sito"
|
||||
|
||||
|
@ -2911,6 +2999,9 @@ msgstr "Qualsiasi"
|
|||
msgid "Yes"
|
||||
msgstr "Sì"
|
||||
|
||||
msgid "Page type"
|
||||
msgstr "Tipologia pagina"
|
||||
|
||||
#, python-format
|
||||
msgid "Page '%(page_title)s' is now unlocked."
|
||||
msgstr "Pagina '%(page_title)s' sbloccata."
|
||||
|
@ -2984,6 +3075,9 @@ msgstr "Data/Ora"
|
|||
msgid "Locked pages"
|
||||
msgstr "Pagine bloccate"
|
||||
|
||||
msgid "Page types usage"
|
||||
msgstr "Utilizzo tipologie pagine"
|
||||
|
||||
msgid "Show"
|
||||
msgstr "Mostra"
|
||||
|
||||
|
@ -3008,6 +3102,9 @@ msgstr "Attività nei flussi di lavoro"
|
|||
msgid "Requested By"
|
||||
msgstr "Richiesto da"
|
||||
|
||||
msgid "Show disabled"
|
||||
msgstr "Mostra disabilitate"
|
||||
|
||||
msgid "Add a workflow"
|
||||
msgstr "Aggiungi un flusso di lavoro"
|
||||
|
||||
|
@ -3041,6 +3138,26 @@ msgstr "Disabilita flusso di lavoro"
|
|||
msgid "Workflow '%(object)s' disabled."
|
||||
msgstr "Flusso di lavoro '%(object)s' disabilitato."
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"This workflow is in progress on %(states_in_progress)d page/snippet. "
|
||||
"Disabling this workflow will cancel moderation on this page/snippet."
|
||||
msgid_plural ""
|
||||
"This workflow is in progress on %(states_in_progress)d pages/snippets. "
|
||||
"Disabling this workflow will cancel moderation on these pages/snippets."
|
||||
msgstr[0] ""
|
||||
"Questo flusso di lavoro è in corso sulla pagina/snippet "
|
||||
"%(states_in_progress)d. Se disabiliti questo flusso di lavoro verrà rimossa "
|
||||
"la moderazione da questa pagina/snippet."
|
||||
msgstr[1] ""
|
||||
"Questo flusso di lavoro è in corso sulle pagine/snippet "
|
||||
"%(states_in_progress)d. Se disabiliti questo flusso di lavoro verrà rimossa "
|
||||
"la moderazione da queste pagine/snippet."
|
||||
msgstr[2] ""
|
||||
"Questo flusso di lavoro è in corso sulle pagine/snippet "
|
||||
"%(states_in_progress)d. Se disabiliti questo flusso di lavoro verrà rimossa "
|
||||
"la moderazione da queste pagine/snippet."
|
||||
|
||||
#, python-format
|
||||
msgid "Workflow '%(workflow_name)s' enabled."
|
||||
msgstr "Flusso di lavoro '%(workflow_name)s' abilitato."
|
||||
|
@ -3148,6 +3265,12 @@ msgstr "Cosa c'è di nuovo in Wagtail %(version)s"
|
|||
msgid "Editor Guide"
|
||||
msgstr "Guida editor"
|
||||
|
||||
msgid "Shortcuts"
|
||||
msgstr "Scorciatoie"
|
||||
|
||||
msgid "Help"
|
||||
msgstr "Aiuto"
|
||||
|
||||
msgid "Choose an item"
|
||||
msgstr "Scegli un elemento"
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import json
|
||||
import warnings
|
||||
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.forms import Media, widgets
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import gettext_lazy
|
||||
|
||||
from wagtail.admin.rich_text.converters.contentstate import ContentstateConverter
|
||||
from wagtail.admin.staticfiles import versioned_static
|
||||
|
@ -13,23 +12,6 @@ from wagtail.telepath import register
|
|||
from wagtail.widget_adapters import WidgetAdapter
|
||||
|
||||
|
||||
class LazyStringEncoder(json.JSONEncoder):
|
||||
"""
|
||||
Add support for lazy strings to the JSON encoder so that URLs and
|
||||
translations can be resolved when rendering the widget only.
|
||||
"""
|
||||
|
||||
# The string "Edit" here is arbitrary, chosen because it exists elsewhere in the
|
||||
# translations dictionary and is likely to remain in the future.
|
||||
lazy_string_types = [type(reverse_lazy("Edit")), type(gettext_lazy("Edit"))]
|
||||
|
||||
def default(self, obj):
|
||||
if type(obj) in self.lazy_string_types:
|
||||
return str(obj)
|
||||
|
||||
return json.JSONEncoder.default(self, obj)
|
||||
|
||||
|
||||
class DraftailRichTextArea(widgets.HiddenInput):
|
||||
template_name = "wagtailadmin/widgets/draftail_rich_text_area.html"
|
||||
is_hidden = False
|
||||
|
@ -90,7 +72,7 @@ class DraftailRichTextArea(widgets.HiddenInput):
|
|||
context = super().get_context(name, value, attrs)
|
||||
context["widget"]["attrs"]["data-w-init-detail-value"] = json.dumps(
|
||||
self.options,
|
||||
cls=LazyStringEncoder,
|
||||
cls=DjangoJSONEncoder,
|
||||
)
|
||||
return context
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<div class="title-wrapper">
|
||||
{% if page.is_site_root %}
|
||||
{% if perms.wagtailcore.add_site or perms.wagtailcore.change_site or perms.wagtailcore.delete_site %}
|
||||
<a href="{% url 'wagtailsites:index' %}" title="{% trans 'Sites menu' %}">{% icon name="site" classname="initial" %}</a>
|
||||
<a href="{% url 'wagtailsites:index' %}" title="{% trans 'Sites menu' %}" class="w-flex w-items-center">{% icon name="site" classname="initial" %}</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
|||
without also reading out the buttons and indicators.
|
||||
{% endcomment %}
|
||||
{% fragment as page_title %}
|
||||
<span id="page_{{ page.pk|unlocalize|admin_urlquote }}_title">
|
||||
<span id="page_{{ page.pk|unlocalize|admin_urlquote }}_title" class="w-flex w-items-center w-gap-2">
|
||||
{% if not page.is_site_root and not page.is_leaf %}{% icon name="folder" classname="initial" %}{% endif %}
|
||||
{{ page.get_admin_display_title }}
|
||||
</span>
|
||||
|
|
|
@ -133,13 +133,15 @@ class TestPageExplorer(WagtailTestUtils, TestCase):
|
|||
def test_explore_root_shows_icon(self):
|
||||
response = self.client.get(reverse("wagtailadmin_explore_root"))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
soup = self.get_soup(response.content)
|
||||
|
||||
# Administrator (or user with add_site permission) should see the
|
||||
# sites link with its icon
|
||||
self.assertContains(
|
||||
response,
|
||||
'<a href="/admin/sites/" title="Sites menu"><svg',
|
||||
)
|
||||
url = reverse("wagtailsites:index")
|
||||
link = soup.select_one(f'td a[href="{url}"]')
|
||||
self.assertIsNotNone(link)
|
||||
icon = link.select_one("svg use[href='#icon-site']")
|
||||
self.assertIsNotNone(icon)
|
||||
|
||||
def test_ordering(self):
|
||||
response = self.client.get(
|
||||
|
|
|
@ -1571,6 +1571,35 @@ class TestEditHandler(WagtailTestUtils, TestCase):
|
|||
self.assertIsNotNone(rendered_heading)
|
||||
self.assertEqual(rendered_heading.text.strip(), expected_heading)
|
||||
|
||||
def test_field_permissions(self):
|
||||
self.user.is_superuser = False
|
||||
self.user.save()
|
||||
self.user.user_permissions.add(
|
||||
Permission.objects.get(
|
||||
content_type__app_label="wagtailadmin", codename="access_admin"
|
||||
),
|
||||
Permission.objects.get(
|
||||
content_type__app_label=self.object._meta.app_label,
|
||||
codename=get_permission_codename("change", self.object._meta),
|
||||
),
|
||||
)
|
||||
|
||||
response = self.client.get(self.url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(list(response.context["form"].fields), ["name"])
|
||||
|
||||
self.user.user_permissions.add(
|
||||
Permission.objects.get(
|
||||
codename="can_set_release_date",
|
||||
)
|
||||
)
|
||||
|
||||
response = self.client.get(self.url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(
|
||||
list(response.context["form"].fields), ["name", "release_date"]
|
||||
)
|
||||
|
||||
|
||||
class TestDefaultMessages(WagtailTestUtils, TestCase):
|
||||
def setUp(self):
|
||||
|
|
|
@ -518,7 +518,3 @@ class Table(Component):
|
|||
@cached_property
|
||||
def attrs(self):
|
||||
return self.table.get_row_attrs(self.instance)
|
||||
|
||||
|
||||
class InlineActionsTable(Table):
|
||||
classname = "listing listing--inline-actions"
|
||||
|
|
|
@ -16,7 +16,7 @@ from wagtail.admin.filters import (
|
|||
MultipleUserFilter,
|
||||
WagtailFilterSet,
|
||||
)
|
||||
from wagtail.admin.ui.tables import Column, DateColumn, InlineActionsTable, UserColumn
|
||||
from wagtail.admin.ui.tables import Column, DateColumn, UserColumn
|
||||
from wagtail.admin.utils import get_latest_str
|
||||
from wagtail.admin.views.generic.base import (
|
||||
BaseListingView,
|
||||
|
@ -197,7 +197,6 @@ class HistoryView(PermissionCheckedMixin, BaseObjectMixin, BaseListingView):
|
|||
is_searchable = False
|
||||
paginate_by = 20
|
||||
filterset_class = HistoryFilterSet
|
||||
table_class = InlineActionsTable
|
||||
history_url_name = None
|
||||
history_results_url_name = None
|
||||
edit_url_name = None
|
||||
|
|
|
@ -28,6 +28,7 @@ from django.views.generic.edit import (
|
|||
from wagtail.actions.unpublish import UnpublishAction
|
||||
from wagtail.admin import messages
|
||||
from wagtail.admin.filters import WagtailFilterSet
|
||||
from wagtail.admin.forms.models import WagtailAdminModelForm
|
||||
from wagtail.admin.forms.search import SearchForm
|
||||
from wagtail.admin.panels import get_edit_handler
|
||||
from wagtail.admin.ui.components import Component, MediaContainer
|
||||
|
@ -628,6 +629,24 @@ class CreateView(
|
|||
for locale in Locale.objects.all().exclude(id=self.locale.id)
|
||||
]
|
||||
|
||||
def get_initial_form_instance(self):
|
||||
if self.locale:
|
||||
instance = self.model()
|
||||
instance.locale = self.locale
|
||||
return instance
|
||||
|
||||
def get_form_kwargs(self):
|
||||
if instance := self.get_initial_form_instance():
|
||||
# super().get_form_kwargs() will use self.object as the instance kwarg
|
||||
self.object = instance
|
||||
kwargs = super().get_form_kwargs()
|
||||
|
||||
form_class = self.get_form_class()
|
||||
# Add for_user support for PermissionedForm
|
||||
if issubclass(form_class, WagtailAdminModelForm):
|
||||
kwargs["for_user"] = self.request.user
|
||||
return kwargs
|
||||
|
||||
def save_instance(self):
|
||||
"""
|
||||
Called after the form is successfully validated - saves the object to the db
|
||||
|
@ -716,9 +735,9 @@ class EditView(
|
|||
return self.actions
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
if "pk" not in self.kwargs:
|
||||
self.kwargs["pk"] = self.args[0]
|
||||
self.kwargs["pk"] = unquote(str(self.kwargs["pk"]))
|
||||
if self.pk_url_kwarg not in self.kwargs:
|
||||
self.kwargs[self.pk_url_kwarg] = self.args[0]
|
||||
self.kwargs[self.pk_url_kwarg] = unquote(str(self.kwargs[self.pk_url_kwarg]))
|
||||
return super().get_object(queryset)
|
||||
|
||||
def get_page_subtitle(self):
|
||||
|
@ -802,6 +821,13 @@ class EditView(
|
|||
for translation in self.object.get_translations().select_related("locale")
|
||||
]
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
form_class = self.get_form_class()
|
||||
if issubclass(form_class, WagtailAdminModelForm):
|
||||
kwargs["for_user"] = self.request.user
|
||||
return kwargs
|
||||
|
||||
def save_instance(self):
|
||||
"""
|
||||
Called after the form is successfully validated - saves the object to the db.
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-04-18 17:28+0100\n"
|
||||
"POT-Creation-Date: 2024-05-02 10:04+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-04-18 17:28+0100\n"
|
||||
"POT-Creation-Date: 2024-05-02 10:04+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
@ -22,20 +22,19 @@ msgstr ""
|
|||
msgid "Wagtail redirects"
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:12 templates/wagtailredirects/list.html:20 views.py:102
|
||||
#: views.py:466
|
||||
#: filters.py:26 views.py:102 views.py:119
|
||||
msgid "Type"
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:15
|
||||
#: filters.py:29
|
||||
msgid "Permanent"
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:16
|
||||
#: filters.py:30
|
||||
msgid "Temporary"
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:18
|
||||
#: filters.py:32
|
||||
msgid "All"
|
||||
msgstr ""
|
||||
|
||||
|
@ -68,59 +67,59 @@ msgstr ""
|
|||
msgid "To field"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:12
|
||||
#: models.py:13
|
||||
msgid "redirect from"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:16
|
||||
#: models.py:17
|
||||
msgid "site"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:24 models.py:84
|
||||
#: models.py:25 models.py:85
|
||||
msgid "permanent"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:27
|
||||
#: models.py:28
|
||||
msgid ""
|
||||
"Recommended. Permanent redirects ensure search engines forget the old page "
|
||||
"(the 'Redirect from') and index the new page instead."
|
||||
msgstr ""
|
||||
|
||||
#: models.py:33
|
||||
#: models.py:34
|
||||
msgid "redirect to a page"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:39
|
||||
#: models.py:40
|
||||
msgid "target page route"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:41
|
||||
#: models.py:42
|
||||
msgid ""
|
||||
"Optionally specify a route on the target page to redirect to. Leave blank to "
|
||||
"redirect to the default page route."
|
||||
msgstr ""
|
||||
|
||||
#: models.py:48
|
||||
#: models.py:49
|
||||
msgid "redirect to any URL"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:51
|
||||
#: models.py:52
|
||||
msgid "automatically created"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:56
|
||||
#: models.py:57
|
||||
msgid "created at"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:86
|
||||
#: models.py:87
|
||||
msgid "temporary"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:203
|
||||
#: models.py:204
|
||||
msgid "redirect"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:204
|
||||
#: models.py:205
|
||||
msgid "redirects"
|
||||
msgstr ""
|
||||
|
||||
|
@ -130,7 +129,6 @@ msgid "Add redirect"
|
|||
msgstr ""
|
||||
|
||||
#: templates/wagtailredirects/add.html:19
|
||||
#: templates/wagtailredirects/edit.html:19
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
|
||||
|
@ -141,7 +139,7 @@ msgstr ""
|
|||
|
||||
#: templates/wagtailredirects/choose_import_file.html:6
|
||||
#: templates/wagtailredirects/confirm_import.html:6
|
||||
#: templates/wagtailredirects/import_summary.html:5 views.py:116
|
||||
#: templates/wagtailredirects/import_summary.html:5 views.py:130
|
||||
msgid "Import redirects"
|
||||
msgstr ""
|
||||
|
||||
|
@ -191,19 +189,6 @@ msgstr ""
|
|||
msgid "Preview"
|
||||
msgstr ""
|
||||
|
||||
#: templates/wagtailredirects/edit.html:3
|
||||
#, python-format
|
||||
msgid "Editing %(title)s"
|
||||
msgstr ""
|
||||
|
||||
#: templates/wagtailredirects/edit.html:5
|
||||
msgid "Editing"
|
||||
msgstr ""
|
||||
|
||||
#: templates/wagtailredirects/edit.html:21
|
||||
msgid "Delete redirect"
|
||||
msgstr ""
|
||||
|
||||
#: templates/wagtailredirects/import_summary.html:3
|
||||
#: templates/wagtailredirects/import_summary.html:6
|
||||
msgid "Summary"
|
||||
|
@ -215,14 +200,11 @@ msgid ""
|
|||
"Found %(total)s redirects, created %(successes)s and found %(errors)s errors."
|
||||
msgstr ""
|
||||
|
||||
#: templates/wagtailredirects/import_summary.html:17
|
||||
#: templates/wagtailredirects/list.html:12
|
||||
#: templates/wagtailredirects/list.html:15 views.py:84 views.py:463
|
||||
#: templates/wagtailredirects/import_summary.html:17 views.py:84 views.py:116
|
||||
msgid "From"
|
||||
msgstr ""
|
||||
|
||||
#: templates/wagtailredirects/import_summary.html:18
|
||||
#: templates/wagtailredirects/list.html:19 views.py:96 views.py:465
|
||||
#: templates/wagtailredirects/import_summary.html:18 views.py:96 views.py:118
|
||||
msgid "To"
|
||||
msgstr ""
|
||||
|
||||
|
@ -246,76 +228,59 @@ msgid ""
|
|||
"href=\"%(wagtailredirects_add_redirect_url)s\">add one</a>?"
|
||||
msgstr ""
|
||||
|
||||
#: templates/wagtailredirects/list.html:18 views.py:90 views.py:464
|
||||
#: views.py:90 views.py:117
|
||||
msgid "Site"
|
||||
msgstr ""
|
||||
|
||||
#: templates/wagtailredirects/list.html:27
|
||||
msgid "Edit this redirect"
|
||||
#: views.py:148
|
||||
msgid "The redirect could not be saved due to errors."
|
||||
msgstr ""
|
||||
|
||||
#: templates/wagtailredirects/reports/redirects_report.html:8
|
||||
#: templates/wagtailredirects/reports/redirects_report.html:13
|
||||
msgid "No redirects found."
|
||||
msgstr ""
|
||||
|
||||
#: views.py:122
|
||||
msgid "Export redirects"
|
||||
msgstr ""
|
||||
|
||||
#: views.py:147
|
||||
#: views.py:153
|
||||
#, python-format
|
||||
msgid "Redirect '%(redirect_title)s' updated."
|
||||
msgstr ""
|
||||
|
||||
#: views.py:152 views.py:220
|
||||
msgid "Edit"
|
||||
msgstr ""
|
||||
|
||||
#: views.py:158
|
||||
msgid "The redirect could not be saved due to errors."
|
||||
msgstr ""
|
||||
|
||||
#: views.py:190
|
||||
#: views.py:173
|
||||
#, python-format
|
||||
msgid "Redirect '%(redirect_title)s' deleted."
|
||||
msgstr ""
|
||||
|
||||
#: views.py:215
|
||||
#: views.py:198
|
||||
#, python-format
|
||||
msgid "Redirect '%(redirect_title)s' added."
|
||||
msgstr ""
|
||||
|
||||
#: views.py:227
|
||||
#: views.py:203
|
||||
msgid "Edit"
|
||||
msgstr ""
|
||||
|
||||
#: views.py:210
|
||||
msgid "The redirect could not be created due to errors."
|
||||
msgstr ""
|
||||
|
||||
#: views.py:266
|
||||
#: views.py:249
|
||||
msgid "Search redirects"
|
||||
msgstr ""
|
||||
|
||||
#: views.py:280
|
||||
#: views.py:263
|
||||
#, python-format
|
||||
msgid "File format of type \"%(extension)s\" is not supported"
|
||||
msgstr ""
|
||||
|
||||
#: views.py:297
|
||||
#: views.py:280
|
||||
#, python-format
|
||||
msgid "Imported file has a wrong encoding: %(error_message)s"
|
||||
msgstr ""
|
||||
|
||||
#: views.py:304
|
||||
#: views.py:287
|
||||
#, python-format
|
||||
msgid "%(error)s encountered while trying to read file: %(filename)s"
|
||||
msgstr ""
|
||||
|
||||
#: views.py:395
|
||||
#: views.py:378
|
||||
#, python-format
|
||||
msgid "Imported %(total)d redirect"
|
||||
msgid_plural "Imported %(total)d redirects"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: views.py:451
|
||||
msgid "Export Redirects"
|
||||
msgstr ""
|
||||
|
|
|
@ -1,38 +1 @@
|
|||
{% extends "wagtailadmin/base.html" %}
|
||||
{% load i18n wagtailadmin_tags %}
|
||||
{% block titletag %}{% blocktrans trimmed with title=redirect.title %}Editing {{ title }}{% endblocktrans %}{% endblock %}
|
||||
{% block content %}
|
||||
{% trans "Editing" as editing_str %}
|
||||
{% include "wagtailadmin/shared/header.html" with title=editing_str subtitle=redirect.title icon="redirect" %}
|
||||
|
||||
{% include "wagtailadmin/shared/non_field_errors.html" %}
|
||||
|
||||
<form action="{% url 'wagtailredirects:edit' redirect.id %}" method="POST" class="nice-padding" novalidate>
|
||||
{% csrf_token %}
|
||||
|
||||
<ul class="fields">
|
||||
{% for field in form.visible_fields %}
|
||||
<li>{% formattedfield field %}</li>
|
||||
{% endfor %}
|
||||
|
||||
<li>
|
||||
<input type="submit" value="{% trans 'Save' %}" class="button" />
|
||||
{% if user_can_delete %}
|
||||
<a href="{% url 'wagtailredirects:delete' redirect.id %}" class="button no">{% trans "Delete redirect" %}</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
{{ block.super }}
|
||||
{% include "wagtailadmin/pages/_editor_js.html" %}
|
||||
{{ form.media.js }}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
{{ block.super }}
|
||||
{{ form.media.css }}
|
||||
{% endblock %}
|
||||
{% extends "wagtailadmin/generic/edit.html" %}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from io import BytesIO
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.test import TestCase, override_settings
|
||||
from django.urls import reverse
|
||||
from openpyxl.reader.excel import load_workbook
|
||||
|
@ -948,7 +949,7 @@ class TestRedirectsAddView(WagtailTestUtils, TestCase):
|
|||
self.assertIsNone(redirects.first().site)
|
||||
|
||||
|
||||
class TestRedirectsEditView(WagtailTestUtils, TestCase):
|
||||
class TestRedirectsEditView(AdminTemplateTestUtils, WagtailTestUtils, TestCase):
|
||||
def setUp(self):
|
||||
# Create a redirect to edit
|
||||
self.redirect = models.Redirect(
|
||||
|
@ -975,6 +976,13 @@ class TestRedirectsEditView(WagtailTestUtils, TestCase):
|
|||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, "wagtailredirects/edit.html")
|
||||
self.assertBreadcrumbsItemsRendered(
|
||||
[
|
||||
{"url": reverse("wagtailredirects:index"), "label": "Redirects"},
|
||||
{"url": "", "label": "/test"},
|
||||
],
|
||||
response.content,
|
||||
)
|
||||
|
||||
url_finder = AdminURLFinder(self.user)
|
||||
expected_url = "/admin/redirects/%d/" % self.redirect.id
|
||||
|
@ -1056,6 +1064,39 @@ class TestRedirectsEditView(WagtailTestUtils, TestCase):
|
|||
# Should not redirect to index
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_get_with_no_permission(self, redirect_id=None):
|
||||
self.user.is_superuser = False
|
||||
self.user.save()
|
||||
# Only basic access_admin permission is given
|
||||
self.user.user_permissions.add(
|
||||
Permission.objects.get(
|
||||
content_type__app_label="wagtailadmin",
|
||||
codename="access_admin",
|
||||
)
|
||||
)
|
||||
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertRedirects(response, reverse("wagtailadmin_home"))
|
||||
|
||||
def test_get_with_edit_permission_only(self):
|
||||
self.user.is_superuser = False
|
||||
self.user.save()
|
||||
self.user.user_permissions.add(
|
||||
Permission.objects.get(
|
||||
content_type__app_label="wagtailadmin",
|
||||
codename="access_admin",
|
||||
),
|
||||
Permission.objects.get(
|
||||
content_type__app_label="wagtailredirects",
|
||||
codename="change_redirect",
|
||||
),
|
||||
)
|
||||
|
||||
response = self.get()
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, "wagtailredirects/edit.html")
|
||||
|
||||
|
||||
class TestRedirectsDeleteView(WagtailTestUtils, TestCase):
|
||||
def setUp(self):
|
||||
|
|
|
@ -7,7 +7,7 @@ urlpatterns = [
|
|||
path("", views.IndexView.as_view(), name="index"),
|
||||
path("results/", views.IndexView.as_view(results_only=True), name="index_results"),
|
||||
path("add/", views.add, name="add"),
|
||||
path("<int:redirect_id>/", views.edit, name="edit"),
|
||||
path("<int:redirect_id>/", views.EditView.as_view(), name="edit"),
|
||||
path("<int:redirect_id>/delete/", views.delete, name="delete"),
|
||||
path("import/", views.start_import, name="start_import"),
|
||||
path("import/process/", views.process_import, name="process_import"),
|
||||
|
|
|
@ -136,49 +136,23 @@ class IndexView(generic.IndexView):
|
|||
return buttons
|
||||
|
||||
|
||||
@permission_checker.require("change")
|
||||
def edit(request, redirect_id):
|
||||
theredirect = get_object_or_404(models.Redirect, id=redirect_id)
|
||||
class EditView(generic.EditView):
|
||||
model = Redirect
|
||||
form_class = RedirectForm
|
||||
permission_policy = permission_policy
|
||||
template_name = "wagtailredirects/edit.html"
|
||||
index_url_name = "wagtailredirects:index"
|
||||
edit_url_name = "wagtailredirects:edit"
|
||||
delete_url_name = "wagtailredirects:delete"
|
||||
pk_url_kwarg = "redirect_id"
|
||||
error_message = gettext_lazy("The redirect could not be saved due to errors.")
|
||||
header_icon = "redirect"
|
||||
_show_breadcrumbs = True
|
||||
|
||||
if not permission_policy.user_has_permission_for_instance(
|
||||
request.user, "change", theredirect
|
||||
):
|
||||
raise PermissionDenied
|
||||
|
||||
if request.method == "POST":
|
||||
form = RedirectForm(request.POST, request.FILES, instance=theredirect)
|
||||
if form.is_valid():
|
||||
with transaction.atomic():
|
||||
form.save()
|
||||
log(instance=theredirect, action="wagtail.edit")
|
||||
messages.success(
|
||||
request,
|
||||
_("Redirect '%(redirect_title)s' updated.")
|
||||
% {"redirect_title": theredirect.title},
|
||||
buttons=[
|
||||
messages.button(
|
||||
reverse("wagtailredirects:edit", args=(theredirect.id,)),
|
||||
_("Edit"),
|
||||
)
|
||||
],
|
||||
)
|
||||
return redirect("wagtailredirects:index")
|
||||
else:
|
||||
messages.error(request, _("The redirect could not be saved due to errors."))
|
||||
else:
|
||||
form = RedirectForm(instance=theredirect)
|
||||
|
||||
return TemplateResponse(
|
||||
request,
|
||||
"wagtailredirects/edit.html",
|
||||
{
|
||||
"redirect": theredirect,
|
||||
"form": form,
|
||||
"user_can_delete": permission_policy.user_has_permission(
|
||||
request.user, "delete"
|
||||
),
|
||||
},
|
||||
)
|
||||
def get_success_message(self):
|
||||
return _("Redirect '%(redirect_title)s' updated.") % {
|
||||
"redirect_title": self.object.title
|
||||
}
|
||||
|
||||
|
||||
@permission_checker.require("delete")
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-04-18 17:28+0100\n"
|
||||
"POT-Creation-Date: 2024-05-02 10:04+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-04-18 17:28+0100\n"
|
||||
"POT-Creation-Date: 2024-05-02 10:04+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
|
Plik binarny nie jest wyświetlany.
|
@ -21,6 +21,9 @@ msgstr ""
|
|||
"Language: hi\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "default"
|
||||
msgstr "मूलचर"
|
||||
|
||||
#, python-format
|
||||
msgid "%(site_setting)s for %(site)s"
|
||||
msgstr "%(site_setting)s for %(site)s"
|
||||
|
|
|
@ -14,6 +14,7 @@ from wagtail.test.testapp.models import (
|
|||
PanelGenericSettings,
|
||||
TabbedGenericSettings,
|
||||
TestGenericSetting,
|
||||
TestPermissionedGenericSetting,
|
||||
)
|
||||
from wagtail.test.utils import WagtailTestUtils
|
||||
|
||||
|
@ -76,6 +77,11 @@ class BaseTestGenericSettingView(WagtailTestUtils, TestCase):
|
|||
class TestGenericSettingCreateView(BaseTestGenericSettingView):
|
||||
def setUp(self):
|
||||
self.user = self.login()
|
||||
self.user.user_permissions.add(
|
||||
Permission.objects.get(
|
||||
content_type__app_label="wagtailadmin", codename="access_admin"
|
||||
)
|
||||
)
|
||||
|
||||
def test_get_edit(self):
|
||||
response = self.get()
|
||||
|
@ -107,6 +113,38 @@ class TestGenericSettingCreateView(BaseTestGenericSettingView):
|
|||
# Ensure the form supports file uploads
|
||||
self.assertContains(response, 'enctype="multipart/form-data"')
|
||||
|
||||
def test_create_restricted_field_without_permission(self):
|
||||
self.user.is_superuser = False
|
||||
self.user.save()
|
||||
|
||||
self.assertFalse(TestPermissionedGenericSetting.objects.exists())
|
||||
response = self.post(
|
||||
post_data={"sensitive_email": "test@example.com", "title": "test"},
|
||||
setting=TestPermissionedGenericSetting,
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
settings = TestPermissionedGenericSetting.objects.get()
|
||||
self.assertEqual(settings.title, "test")
|
||||
self.assertEqual(settings.sensitive_email, "")
|
||||
|
||||
def test_create_restricted_field(self):
|
||||
self.user.is_superuser = False
|
||||
self.user.save()
|
||||
self.user.user_permissions.add(
|
||||
Permission.objects.get(codename="can_edit_sensitive_email_generic_setting")
|
||||
)
|
||||
self.assertFalse(TestPermissionedGenericSetting.objects.exists())
|
||||
response = self.post(
|
||||
post_data={"sensitive_email": "test@example.com", "title": "test"},
|
||||
setting=TestPermissionedGenericSetting,
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
settings = TestPermissionedGenericSetting.objects.get()
|
||||
self.assertEqual(settings.title, "test")
|
||||
self.assertEqual(settings.sensitive_email, "test@example.com")
|
||||
|
||||
|
||||
class TestGenericSettingEditView(BaseTestGenericSettingView):
|
||||
def setUp(self):
|
||||
|
@ -114,7 +152,12 @@ class TestGenericSettingEditView(BaseTestGenericSettingView):
|
|||
self.test_setting.title = "Setting title"
|
||||
self.test_setting.save()
|
||||
|
||||
self.login()
|
||||
self.user = self.login()
|
||||
self.user.user_permissions.add(
|
||||
Permission.objects.get(
|
||||
content_type__app_label="wagtailadmin", codename="access_admin"
|
||||
)
|
||||
)
|
||||
|
||||
def test_get_edit(self):
|
||||
response = self.get()
|
||||
|
@ -162,6 +205,50 @@ class TestGenericSettingEditView(BaseTestGenericSettingView):
|
|||
expected_url=f"{url}{TestGenericSetting.objects.first().pk}/",
|
||||
)
|
||||
|
||||
def test_edit_restricted_field(self):
|
||||
test_setting = TestPermissionedGenericSetting()
|
||||
test_setting.sensitive_email = "test@example.com"
|
||||
test_setting.save()
|
||||
self.user.is_superuser = False
|
||||
self.user.save()
|
||||
|
||||
self.user.user_permissions.add(
|
||||
Permission.objects.get(codename="can_edit_sensitive_email_generic_setting")
|
||||
)
|
||||
|
||||
response = self.get(setting=TestPermissionedGenericSetting)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("sensitive_email", list(response.context["form"].fields))
|
||||
|
||||
response = self.post(
|
||||
setting=TestPermissionedGenericSetting,
|
||||
post_data={"sensitive_email": "test-updated@example.com", "title": "title"},
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
test_setting.refresh_from_db()
|
||||
self.assertEqual(test_setting.sensitive_email, "test-updated@example.com")
|
||||
|
||||
def test_edit_restricted_field_without_permission(self):
|
||||
test_setting = TestPermissionedGenericSetting()
|
||||
test_setting.sensitive_email = "test@example.com"
|
||||
test_setting.save()
|
||||
self.user.is_superuser = False
|
||||
self.user.save()
|
||||
|
||||
response = self.get(setting=TestPermissionedGenericSetting)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertNotIn("sensitive_email", list(response.context["form"].fields))
|
||||
|
||||
response = self.post(
|
||||
setting=TestPermissionedGenericSetting,
|
||||
post_data={"sensitive_email": "test-updated@example.com", "title": "title"},
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
test_setting.refresh_from_db()
|
||||
self.assertEqual(test_setting.sensitive_email, "test@example.com")
|
||||
|
||||
|
||||
class TestAdminPermission(WagtailTestUtils, TestCase):
|
||||
def test_registered_permission(self):
|
||||
|
|
|
@ -14,6 +14,7 @@ from wagtail.test.testapp.models import (
|
|||
IconSiteSetting,
|
||||
PanelSiteSettings,
|
||||
TabbedSiteSettings,
|
||||
TestPermissionedSiteSetting,
|
||||
TestSiteSetting,
|
||||
)
|
||||
from wagtail.test.utils import WagtailTestUtils
|
||||
|
@ -72,6 +73,11 @@ class BaseTestSiteSettingView(WagtailTestUtils, TestCase):
|
|||
class TestSiteSettingCreateView(BaseTestSiteSettingView):
|
||||
def setUp(self):
|
||||
self.user = self.login()
|
||||
self.user.user_permissions.add(
|
||||
Permission.objects.get(
|
||||
content_type__app_label="wagtailadmin", codename="access_admin"
|
||||
)
|
||||
)
|
||||
|
||||
def test_get_edit(self):
|
||||
response = self.get()
|
||||
|
@ -103,18 +109,55 @@ class TestSiteSettingCreateView(BaseTestSiteSettingView):
|
|||
# Ensure the form supports file uploads
|
||||
self.assertContains(response, 'enctype="multipart/form-data"')
|
||||
|
||||
def test_create_restricted_field_without_permission(self):
|
||||
self.user.is_superuser = False
|
||||
self.user.save()
|
||||
|
||||
self.assertFalse(TestPermissionedSiteSetting.objects.exists())
|
||||
response = self.post(
|
||||
post_data={"sensitive_email": "test@example.com", "title": "test"},
|
||||
setting=TestPermissionedSiteSetting,
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
settings = TestPermissionedSiteSetting.objects.get()
|
||||
self.assertEqual(settings.title, "test")
|
||||
self.assertEqual(settings.sensitive_email, "")
|
||||
|
||||
def test_create_restricted_field(self):
|
||||
self.user.is_superuser = False
|
||||
self.user.save()
|
||||
self.user.user_permissions.add(
|
||||
Permission.objects.get(codename="can_edit_sensitive_email_site_setting")
|
||||
)
|
||||
self.assertFalse(TestPermissionedSiteSetting.objects.exists())
|
||||
response = self.post(
|
||||
post_data={"sensitive_email": "test@example.com", "title": "test"},
|
||||
setting=TestPermissionedSiteSetting,
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
settings = TestPermissionedSiteSetting.objects.get()
|
||||
self.assertEqual(settings.title, "test")
|
||||
self.assertEqual(settings.sensitive_email, "test@example.com")
|
||||
|
||||
|
||||
class TestSiteSettingEditView(BaseTestSiteSettingView):
|
||||
def setUp(self):
|
||||
default_site = Site.objects.get(is_default_site=True)
|
||||
self.default_site = Site.objects.get(is_default_site=True)
|
||||
|
||||
self.test_setting = TestSiteSetting()
|
||||
self.test_setting.title = "Site title"
|
||||
self.test_setting.email = "initial@example.com"
|
||||
self.test_setting.site = default_site
|
||||
self.test_setting.site = self.default_site
|
||||
self.test_setting.save()
|
||||
|
||||
self.login()
|
||||
self.user = self.login()
|
||||
self.user.user_permissions.add(
|
||||
Permission.objects.get(
|
||||
content_type__app_label="wagtailadmin", codename="access_admin"
|
||||
)
|
||||
)
|
||||
|
||||
def test_get_edit(self):
|
||||
response = self.get()
|
||||
|
@ -167,6 +210,52 @@ class TestSiteSettingEditView(BaseTestSiteSettingView):
|
|||
response = self.client.get(url)
|
||||
self.assertRedirects(response, status_code=302, expected_url="/admin/")
|
||||
|
||||
def test_edit_restricted_field(self):
|
||||
test_setting = TestPermissionedSiteSetting()
|
||||
test_setting.sensitive_email = "test@example.com"
|
||||
test_setting.site = self.default_site
|
||||
test_setting.save()
|
||||
self.user.is_superuser = False
|
||||
self.user.save()
|
||||
|
||||
self.user.user_permissions.add(
|
||||
Permission.objects.get(codename="can_edit_sensitive_email_site_setting")
|
||||
)
|
||||
|
||||
response = self.get(setting=TestPermissionedSiteSetting)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("sensitive_email", list(response.context["form"].fields))
|
||||
|
||||
response = self.post(
|
||||
setting=TestPermissionedSiteSetting,
|
||||
post_data={"sensitive_email": "test-updated@example.com", "title": "title"},
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
test_setting.refresh_from_db()
|
||||
self.assertEqual(test_setting.sensitive_email, "test-updated@example.com")
|
||||
|
||||
def test_edit_restricted_field_without_permission(self):
|
||||
test_setting = TestPermissionedSiteSetting()
|
||||
test_setting.sensitive_email = "test@example.com"
|
||||
test_setting.site = self.default_site
|
||||
test_setting.save()
|
||||
self.user.is_superuser = False
|
||||
self.user.save()
|
||||
|
||||
response = self.get(setting=TestPermissionedSiteSetting)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertNotIn("sensitive_email", list(response.context["form"].fields))
|
||||
|
||||
response = self.post(
|
||||
setting=TestPermissionedSiteSetting,
|
||||
post_data={"sensitive_email": "test-updated@example.com", "title": "title"},
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
test_setting.refresh_from_db()
|
||||
self.assertEqual(test_setting.sensitive_email, "test@example.com")
|
||||
|
||||
|
||||
@override_settings(
|
||||
ALLOWED_HOSTS=["testserver", "example.com", "noneoftheabove.example.com"]
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-04-18 17:28+0100\n"
|
||||
"POT-Creation-Date: 2024-05-02 10:04+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-04-18 17:28+0100\n"
|
||||
"POT-Creation-Date: 2024-05-02 10:04+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from wagtail.admin.staticfiles import versioned_static
|
||||
from wagtail.test.utils import WagtailTestUtils
|
||||
|
||||
|
||||
|
@ -13,3 +14,10 @@ class TestStyleGuide(WagtailTestUtils, TestCase):
|
|||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTemplateUsed(response, "wagtailstyleguide/base.html")
|
||||
|
||||
custom_css = versioned_static("wagtailstyleguide/css/animate-progress.css")
|
||||
widget_css = versioned_static("wagtailadmin/css/panels/draftail.css")
|
||||
widget_js = versioned_static("wagtailadmin/js/draftail.js")
|
||||
self.assertContains(response, custom_css)
|
||||
self.assertContains(response, widget_css)
|
||||
self.assertContains(response, widget_js)
|
||||
|
|
|
@ -94,7 +94,7 @@ class ExampleForm(forms.Form):
|
|||
|
||||
@property
|
||||
def media(self):
|
||||
return forms.Media(
|
||||
return super().media + forms.Media(
|
||||
css={
|
||||
"all": [versioned_static("wagtailstyleguide/css/animate-progress.css")]
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-04-18 17:28+0100\n"
|
||||
"POT-Creation-Date: 2024-05-02 10:04+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-04-18 17:28+0100\n"
|
||||
"POT-Creation-Date: 2024-05-02 10:04+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-04-18 17:28+0100\n"
|
||||
"POT-Creation-Date: 2024-05-02 10:04+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-04-18 17:28+0100\n"
|
||||
"POT-Creation-Date: 2024-05-02 10:04+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-04-18 17:28+0100\n"
|
||||
"POT-Creation-Date: 2024-05-02 10:04+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
@ -610,23 +610,23 @@ msgid_plural "%(num_parent_objects)d images have been deleted"
|
|||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: views/chooser.py:51 views/chooser.py:313
|
||||
#: views/chooser.py:51 views/chooser.py:314
|
||||
msgid "Upload"
|
||||
msgstr ""
|
||||
|
||||
#: views/chooser.py:52 views/chooser.py:314
|
||||
#: views/chooser.py:52 views/chooser.py:315
|
||||
msgid "Uploading…"
|
||||
msgstr ""
|
||||
|
||||
#: views/chooser.py:312 widgets.py:13
|
||||
#: views/chooser.py:313 widgets.py:13
|
||||
msgid "Choose an image"
|
||||
msgstr ""
|
||||
|
||||
#: views/chooser.py:315
|
||||
#: views/chooser.py:316
|
||||
msgid "Choose another image"
|
||||
msgstr ""
|
||||
|
||||
#: views/chooser.py:316 widgets.py:15
|
||||
#: views/chooser.py:317 widgets.py:15
|
||||
msgid "Edit this image"
|
||||
msgstr ""
|
||||
|
||||
|
|
Plik binarny nie jest wyświetlany.
|
@ -317,6 +317,23 @@ msgstr "Scegli un formato"
|
|||
msgid "Insert image"
|
||||
msgstr "Inserisci immagine"
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"<span>%(total)s</span> Image <span class=\"w-sr-only\">created in "
|
||||
"%(site_name)s</span>"
|
||||
msgid_plural ""
|
||||
"<span>%(total)s</span> Images <span class=\"w-sr-only\">created in "
|
||||
"%(site_name)s</span>"
|
||||
msgstr[0] ""
|
||||
"<span>%(total)s</span> immagine <span class=\"w-sr-only\">creata in "
|
||||
"%(site_name)s</span>"
|
||||
msgstr[1] ""
|
||||
"<span>%(total)s</span> immagini <span class=\"w-sr-only\">create in "
|
||||
"%(site_name)s</span>"
|
||||
msgstr[2] ""
|
||||
"<span>%(total)s</span> immagini <span class=\"w-sr-only\">create in "
|
||||
"%(site_name)s</span>"
|
||||
|
||||
msgid "Change image file:"
|
||||
msgstr "Cambia immagine"
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-04-18 17:28+0100\n"
|
||||
"POT-Creation-Date: 2024-05-02 10:04+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
|
Plik binarny nie jest wyświetlany.
|
@ -75,6 +75,9 @@ msgstr "Wagtail cache frontend"
|
|||
msgid "Wagtail sitemaps"
|
||||
msgstr "Sitemap Wagtail"
|
||||
|
||||
msgid "Unknown content type"
|
||||
msgstr "Tipo contenuto sconosciuto"
|
||||
|
||||
msgid "Password"
|
||||
msgstr "Password"
|
||||
|
||||
|
@ -87,6 +90,28 @@ msgstr "Commenta"
|
|||
msgid "Locked"
|
||||
msgstr "Bloccata"
|
||||
|
||||
#, python-format
|
||||
msgid "No one can make changes while the %(model_name)s is locked"
|
||||
msgstr "Nessuno può fare modifiche finché %(model_name)s è bloccato"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "<b>'{title}' was locked</b> by <b>you</b> on <b>{datetime}</b>."
|
||||
msgstr "<b>'{title}' è stata bloccata</b> da <b>te</b> il <b>{datetime}</b>."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "<b>'{title}' is locked</b> by <b>you</b>."
|
||||
msgstr "<b>'{title}' è bloccata</b> da <b>te</b>."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "<b>'{title}' is locked</b>."
|
||||
msgstr "<b>'{title}' è bloccata</b>."
|
||||
|
||||
msgid "Locked by you"
|
||||
msgstr "Bloccata da te"
|
||||
|
||||
msgid "Locked by another user"
|
||||
msgstr "Bloccata da un altro utente"
|
||||
|
||||
msgid "Unlocked"
|
||||
msgstr "Sbloccata"
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-04-18 17:28+0100\n"
|
||||
"POT-Creation-Date: 2024-05-02 10:04+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
from django.apps import apps
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import connection, models
|
||||
|
||||
from wagtail.models import (
|
||||
BaseLogEntry,
|
||||
BootstrapTranslatableMixin,
|
||||
ReferenceIndex,
|
||||
TranslatableMixin,
|
||||
)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Converts UUID columns from char type to the native UUID type used in MariaDB 10.7+ and Django 5.0+."
|
||||
|
||||
def convert_field(self, model, field_name, null=False):
|
||||
if model._meta.get_field(field_name).model != model:
|
||||
# Field is inherited from a parent model
|
||||
return
|
||||
|
||||
if not model._meta.managed:
|
||||
# The migration framework skips unmanaged models, so we should too
|
||||
return
|
||||
|
||||
old_field = models.CharField(null=null, max_length=36)
|
||||
old_field.set_attributes_from_name(field_name)
|
||||
|
||||
new_field = models.UUIDField(null=null)
|
||||
new_field.set_attributes_from_name(field_name)
|
||||
|
||||
with connection.schema_editor() as schema_editor:
|
||||
schema_editor.alter_field(model, old_field, new_field)
|
||||
|
||||
def handle(self, **options):
|
||||
self.convert_field(ReferenceIndex, "content_path_hash")
|
||||
|
||||
for model in apps.get_models():
|
||||
if issubclass(model, BaseLogEntry):
|
||||
self.convert_field(model, "uuid", null=True)
|
||||
elif issubclass(model, BootstrapTranslatableMixin):
|
||||
self.convert_field(model, "translation_key", null=True)
|
||||
elif issubclass(model, TranslatableMixin):
|
||||
self.convert_field(model, "translation_key")
|
|
@ -290,7 +290,14 @@ class RevisionMixin(models.Model):
|
|||
"latest_revision",
|
||||
]
|
||||
|
||||
@property
|
||||
_revisions = GenericRelation(
|
||||
"wagtailcore.Revision",
|
||||
content_type_field="content_type",
|
||||
object_id_field="object_id",
|
||||
for_concrete_model=False,
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def revisions(self):
|
||||
"""
|
||||
Returns revisions that belong to the object.
|
||||
|
@ -302,10 +309,7 @@ class RevisionMixin(models.Model):
|
|||
``related_query_name`` of the ``GenericRelation`` and add custom logic
|
||||
(e.g. to always use the specific instance in ``Page``).
|
||||
"""
|
||||
return Revision.objects.filter(
|
||||
content_type=self.get_content_type(),
|
||||
object_id=self.pk,
|
||||
)
|
||||
return Revision.objects.for_instance(self)
|
||||
|
||||
def get_base_content_type(self):
|
||||
parents = self._meta.get_parent_list()
|
||||
|
@ -915,9 +919,19 @@ class LockableMixin(models.Model):
|
|||
return BasicLock(self)
|
||||
|
||||
|
||||
class WorkflowMixin:
|
||||
class WorkflowMixin(models.Model):
|
||||
"""A mixin that allows a model to have workflows."""
|
||||
|
||||
_workflow_states = GenericRelation(
|
||||
"wagtailcore.WorkflowState",
|
||||
content_type_field="base_content_type",
|
||||
object_id_field="object_id",
|
||||
for_concrete_model=False,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@classmethod
|
||||
def check(cls, **kwargs):
|
||||
return [
|
||||
|
@ -990,7 +1004,7 @@ class WorkflowMixin:
|
|||
"""Returns the active workflow assigned to the object."""
|
||||
return self.get_default_workflow()
|
||||
|
||||
@property
|
||||
@cached_property
|
||||
def workflow_states(self):
|
||||
"""
|
||||
Returns workflow states that belong to the object.
|
||||
|
@ -1199,9 +1213,16 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase):
|
|||
verbose_name=_("latest revision created at"), null=True, editable=False
|
||||
)
|
||||
|
||||
_revisions = GenericRelation("wagtailcore.Revision", related_query_name="page")
|
||||
_revisions = GenericRelation(
|
||||
"wagtailcore.Revision",
|
||||
content_type_field="content_type",
|
||||
object_id_field="object_id",
|
||||
related_query_name="page",
|
||||
for_concrete_model=False,
|
||||
)
|
||||
|
||||
# Add GenericRelation to allow WorkflowState.objects.filter(page=...) queries.
|
||||
# Override WorkflowMixin's GenericRelation to specify related_query_name
|
||||
# so we can do WorkflowState.objects.filter(page=...) queries.
|
||||
# There is no need to override the workflow_states property, as the default
|
||||
# implementation in WorkflowMixin already ensures that the queryset uses the
|
||||
# base Page content type.
|
||||
|
@ -1344,7 +1365,7 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase):
|
|||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
@property
|
||||
@cached_property
|
||||
def revisions(self):
|
||||
# Always use the specific page instance when querying for revisions as
|
||||
# they are always saved with the specific content_type.
|
||||
|
@ -1707,7 +1728,7 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase):
|
|||
clean=True,
|
||||
):
|
||||
# Raise error if this is not the specific version of the page
|
||||
if not isinstance(self, self.specific_class):
|
||||
if not self.is_specific:
|
||||
raise RuntimeError(
|
||||
"page.save_revision() must be called on the specific version of the page. "
|
||||
"Call page.specific.save_revision() instead."
|
||||
|
@ -2460,7 +2481,7 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase):
|
|||
and self.specific_class.permissions_for_user
|
||||
!= type(self).permissions_for_user
|
||||
)
|
||||
if is_overridden and not isinstance(self, self.specific_class):
|
||||
if is_overridden and not self.is_specific:
|
||||
return self.specific_deferred.permissions_for_user(user)
|
||||
return PagePermissionTester(user, self)
|
||||
|
||||
|
@ -2789,12 +2810,23 @@ class RevisionQuerySet(models.QuerySet):
|
|||
return self.exclude(self.page_revisions_q())
|
||||
|
||||
def for_instance(self, instance):
|
||||
return self.filter(
|
||||
content_type=ContentType.objects.get_for_model(
|
||||
instance, for_concrete_model=False
|
||||
),
|
||||
object_id=str(instance.pk),
|
||||
)
|
||||
"""
|
||||
Filters to only Revisions for the given instance
|
||||
"""
|
||||
try:
|
||||
# Use RevisionMixin.get_base_content_type() if available
|
||||
return self.filter(
|
||||
base_content_type=instance.get_base_content_type(),
|
||||
object_id=str(instance.pk),
|
||||
)
|
||||
except AttributeError:
|
||||
# Fallback to ContentType for the model
|
||||
return self.filter(
|
||||
content_type=ContentType.objects.get_for_model(
|
||||
instance, for_concrete_model=False
|
||||
),
|
||||
object_id=str(instance.pk),
|
||||
)
|
||||
|
||||
|
||||
class RevisionsManager(models.Manager.from_queryset(RevisionQuerySet)):
|
||||
|
|
|
@ -118,6 +118,10 @@ class SpecificMixin:
|
|||
"""
|
||||
return self.cached_content_type.model_class()
|
||||
|
||||
@property
|
||||
def is_specific(self):
|
||||
return self.specific_class is not None and isinstance(self, self.specific_class)
|
||||
|
||||
@property
|
||||
def cached_content_type(self):
|
||||
"""
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-04-18 17:28+0100\n"
|
||||
"POT-Creation-Date: 2024-05-02 10:04+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-04-18 17:28+0100\n"
|
||||
"POT-Creation-Date: 2024-05-02 10:04+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-04-18 17:28+0100\n"
|
||||
"POT-Creation-Date: 2024-05-02 10:04+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
@ -182,7 +182,7 @@ msgstr ""
|
|||
msgid "Choose"
|
||||
msgstr ""
|
||||
|
||||
#: views/snippets.py:77 views/snippets.py:104 views/snippets.py:913
|
||||
#: views/snippets.py:77 views/snippets.py:104 views/snippets.py:894
|
||||
#: wagtail_hooks.py:44
|
||||
msgid "Snippets"
|
||||
msgstr ""
|
||||
|
@ -200,17 +200,17 @@ msgstr ""
|
|||
msgid "More options for '%(title)s'"
|
||||
msgstr ""
|
||||
|
||||
#: views/snippets.py:389
|
||||
#: views/snippets.py:370
|
||||
#, python-format
|
||||
msgid "Edit this %(model_name)s"
|
||||
msgstr ""
|
||||
|
||||
#: views/snippets.py:395
|
||||
#: views/snippets.py:376
|
||||
#, python-format
|
||||
msgid "%(model_name)s history"
|
||||
msgstr ""
|
||||
|
||||
#: views/snippets.py:912
|
||||
#: views/snippets.py:893
|
||||
msgid "Home"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -233,22 +233,6 @@ class CreateView(generic.CreateEditViewOptionalFeaturesMixin, generic.CreateView
|
|||
def _get_action_menu(self):
|
||||
return SnippetActionMenu(self.request, view=self.view_name, model=self.model)
|
||||
|
||||
def _get_initial_form_instance(self):
|
||||
instance = self.model()
|
||||
|
||||
# Set locale of the new instance
|
||||
if self.locale:
|
||||
instance.locale = self.locale
|
||||
|
||||
return instance
|
||||
|
||||
def get_form_kwargs(self):
|
||||
return {
|
||||
**super().get_form_kwargs(),
|
||||
"instance": self._get_initial_form_instance(),
|
||||
"for_user": self.request.user,
|
||||
}
|
||||
|
||||
def get_side_panels(self):
|
||||
side_panels = [
|
||||
SnippetStatusSidePanel(
|
||||
|
@ -308,9 +292,6 @@ class EditView(generic.CreateEditViewOptionalFeaturesMixin, generic.EditView):
|
|||
locked_for_user=self.locked_for_user,
|
||||
)
|
||||
|
||||
def get_form_kwargs(self):
|
||||
return {**super().get_form_kwargs(), "for_user": self.request.user}
|
||||
|
||||
def get_side_panels(self):
|
||||
side_panels = [
|
||||
SnippetStatusSidePanel(
|
||||
|
|
|
@ -3,6 +3,7 @@ from django.shortcuts import resolve_url
|
|||
from django.template.defaulttags import token_kwargs
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.encoding import force_str
|
||||
from django.utils.functional import Promise
|
||||
from django.utils.html import conditional_escape
|
||||
|
||||
from wagtail import VERSION, __version__
|
||||
|
@ -120,6 +121,8 @@ def richtext(value):
|
|||
elif value is None:
|
||||
html = ""
|
||||
else:
|
||||
if isinstance(value, Promise):
|
||||
value = str(value)
|
||||
if isinstance(value, str):
|
||||
html = expand_db_html(value)
|
||||
else:
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
# Generated by Django 4.2.11 on 2024-04-25 15:51
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('wagtailcore', '0093_uploadedfile'),
|
||||
('tests', '0036_complexdefaultstreampage'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='TestPermissionedGenericSetting',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=100)),
|
||||
('sensitive_email', models.EmailField(max_length=50)),
|
||||
],
|
||||
options={
|
||||
'permissions': [('can_edit_sensitive_email_generic_setting', 'Can edit sensitive email generic setting.')],
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='featurecompletetoy',
|
||||
options={'permissions': [('can_set_release_date', 'Can set release date')]},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TestPermissionedSiteSetting',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=100)),
|
||||
('sensitive_email', models.EmailField(max_length=50)),
|
||||
('site', models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, to='wagtailcore.site')),
|
||||
],
|
||||
options={
|
||||
'permissions': [('can_edit_sensitive_email_site_setting', 'Can edit sensitive email site setting.')],
|
||||
},
|
||||
),
|
||||
]
|
|
@ -1685,6 +1685,49 @@ class TestGenericSetting(BaseGenericSetting):
|
|||
email = models.EmailField(max_length=50)
|
||||
|
||||
|
||||
@register_setting
|
||||
class TestPermissionedGenericSetting(BaseGenericSetting):
|
||||
title = models.CharField(max_length=100)
|
||||
sensitive_email = models.EmailField(max_length=50)
|
||||
|
||||
panels = [
|
||||
FieldPanel("title"),
|
||||
FieldPanel(
|
||||
"sensitive_email",
|
||||
permission="tests.can_edit_sensitive_email_generic_setting",
|
||||
),
|
||||
]
|
||||
|
||||
class Meta:
|
||||
permissions = [
|
||||
(
|
||||
"can_edit_sensitive_email_generic_setting",
|
||||
"Can edit sensitive email generic setting.",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@register_setting
|
||||
class TestPermissionedSiteSetting(BaseSiteSetting):
|
||||
title = models.CharField(max_length=100)
|
||||
sensitive_email = models.EmailField(max_length=50)
|
||||
|
||||
panels = [
|
||||
FieldPanel("title"),
|
||||
FieldPanel(
|
||||
"sensitive_email", permission="tests.can_edit_sensitive_email_site_setting"
|
||||
),
|
||||
]
|
||||
|
||||
class Meta:
|
||||
permissions = [
|
||||
(
|
||||
"can_edit_sensitive_email_site_setting",
|
||||
"Can edit sensitive email site setting.",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@register_setting
|
||||
class ImportantPagesSiteSetting(BaseSiteSetting):
|
||||
sign_up_page = models.ForeignKey(
|
||||
|
@ -2274,6 +2317,9 @@ class FeatureCompleteToy(index.Indexed, models.Model):
|
|||
def __str__(self):
|
||||
return f"{self.name} ({self.release_date})"
|
||||
|
||||
class Meta:
|
||||
permissions = [("can_set_release_date", "Can set release date")]
|
||||
|
||||
|
||||
class PurgeRevisionsProtectedTestModel(models.Model):
|
||||
revision = models.OneToOneField(
|
||||
|
|
|
@ -229,7 +229,7 @@ class FeatureCompleteToyViewSet(ModelViewSet):
|
|||
|
||||
panels = [
|
||||
FieldPanel("name"),
|
||||
FieldPanel("release_date"),
|
||||
FieldPanel("release_date", permission="tests.can_set_release_date"),
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -173,15 +173,15 @@ class TestRevisableModel(TestCase):
|
|||
def test_revision_cascade_on_object_delete(self):
|
||||
page = self.create_page()
|
||||
full_featured_snippet = FullFeaturedSnippet.objects.create(text="foo")
|
||||
# The RevisionMixin should provide a default `GenericRelation` so that
|
||||
# revisions are deleted when the object is deleted, even if the
|
||||
# model does not explicitly define a `GenericRelation` to `Revision`.
|
||||
cases = [
|
||||
# Tuple of (instance, cascades)
|
||||
# For models that define a GenericRelation to Revision, the revision
|
||||
# should be deleted when the instance is deleted.
|
||||
(page, True),
|
||||
(full_featured_snippet, True),
|
||||
(self.instance, False), # No GenericRelation to Revision
|
||||
page,
|
||||
full_featured_snippet,
|
||||
self.instance, # No explicit GenericRelation to Revision
|
||||
]
|
||||
for instance, cascades in cases:
|
||||
for instance in cases:
|
||||
with self.subTest(instance=instance):
|
||||
revision = instance.save_revision()
|
||||
query = {
|
||||
|
@ -190,4 +190,4 @@ class TestRevisableModel(TestCase):
|
|||
}
|
||||
self.assertEqual(Revision.objects.filter(**query).first(), revision)
|
||||
instance.delete()
|
||||
self.assertIs(Revision.objects.filter(**query).exists(), not cascades)
|
||||
self.assertIs(Revision.objects.filter(**query).exists(), False)
|
||||
|
|
|
@ -449,7 +449,7 @@ class TestPageWorkflows(WagtailTestUtils, TestCase):
|
|||
self.assertIsNone(self.object.locked_at)
|
||||
self.assertIsNone(self.object.locked_by)
|
||||
|
||||
def test_workflow_state_cascade_on_object_delete(self, cascades=True):
|
||||
def test_workflow_state_cascade_on_object_delete(self):
|
||||
data = self.start_workflow()
|
||||
query = {
|
||||
"base_content_type": self.object.get_base_content_type(),
|
||||
|
@ -460,7 +460,7 @@ class TestPageWorkflows(WagtailTestUtils, TestCase):
|
|||
data["workflow_state"],
|
||||
)
|
||||
self.object.delete()
|
||||
self.assertIs(WorkflowState.objects.filter(**query).exists(), not cascades)
|
||||
self.assertIs(WorkflowState.objects.filter(**query).exists(), False)
|
||||
|
||||
|
||||
class TestSnippetWorkflows(TestPageWorkflows):
|
||||
|
@ -493,9 +493,6 @@ class TestSnippetWorkflowsNotLockable(TestSnippetWorkflows):
|
|||
self.assertEqual(workflow_state.content_object, self.object)
|
||||
self.assertEqual(workflow_state.status, "in_progress")
|
||||
|
||||
def test_workflow_state_cascade_on_object_delete(self):
|
||||
# We expect the cascade to not happen as the model does not define
|
||||
# a GenericRelation to WorkflowState. However, workflows should still
|
||||
# work as expected.
|
||||
# See https://github.com/wagtail/wagtail/issues/11300 for more details.
|
||||
return super().test_workflow_state_cascade_on_object_delete(cascades=False)
|
||||
# The ModeratedModel does not explicitly define a `GenericRelation` to
|
||||
# `WorkflowState`, but the `WorkflowState` should still be deleted when the
|
||||
# object is deleted (test_workflow_state_cascade_on_object_delete passes).
|
||||
|
|
|
@ -9,6 +9,7 @@ from django.test import TestCase
|
|||
from django.test.utils import override_settings
|
||||
from django.urls.exceptions import NoReverseMatch
|
||||
from django.utils.safestring import SafeString
|
||||
from django.utils.translation import gettext_lazy
|
||||
|
||||
from wagtail.coreutils import (
|
||||
get_dummy_request,
|
||||
|
@ -536,6 +537,10 @@ class TestRichtextTag(TestCase):
|
|||
self.assertEqual(result, "Hello world!")
|
||||
self.assertIsInstance(result, SafeString)
|
||||
|
||||
def test_call_with_lazy(self):
|
||||
result = richtext(gettext_lazy("test"))
|
||||
self.assertEqual(result, "test")
|
||||
|
||||
def test_call_with_none(self):
|
||||
result = richtext(None)
|
||||
self.assertEqual(result, "")
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-04-18 17:28+0100\n"
|
||||
"POT-Creation-Date: 2024-05-02 10:04+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
@ -160,15 +160,16 @@ msgstr ""
|
|||
msgid "Snug"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:92
|
||||
#. Translators: "Density" is the term used to describe the amount of space between elements in the user interface
|
||||
#: models.py:93
|
||||
msgid "density"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:112
|
||||
#: models.py:113
|
||||
msgid "user profile"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:113
|
||||
#: models.py:114
|
||||
msgid "user profiles"
|
||||
msgstr ""
|
||||
|
||||
|
|
Plik binarny nie jest wyświetlany.
|
@ -6,7 +6,7 @@
|
|||
# Giacomo Ghizzani <giacomo.ghz@gmail.com>, 2015-2018
|
||||
# giammi <gian-maria.daffre@giammi.org>, 2018
|
||||
# giammi <gian-maria.daffre@giammi.org>, 2018
|
||||
# Marco Badan <marco.badan@gmail.com>, 2021-2023
|
||||
# Marco Badan <marco.badan@gmail.com>, 2021-2024
|
||||
# Sandro Badalamenti <sandro@mailinator.com>, 2019
|
||||
msgid ""
|
||||
msgstr ""
|
||||
|
@ -14,7 +14,7 @@ msgstr ""
|
|||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-04-18 17:28+0100\n"
|
||||
"PO-Revision-Date: 2014-02-19 11:54+0000\n"
|
||||
"Last-Translator: Marco Badan <marco.badan@gmail.com>, 2021-2023\n"
|
||||
"Last-Translator: Marco Badan <marco.badan@gmail.com>, 2021-2024\n"
|
||||
"Language-Team: Italian (http://app.transifex.com/torchbox/wagtail/language/"
|
||||
"it/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
@ -132,6 +132,12 @@ msgstr "tema admin"
|
|||
msgid "Default"
|
||||
msgstr "Default"
|
||||
|
||||
msgid "Snug"
|
||||
msgstr "Compatta"
|
||||
|
||||
msgid "density"
|
||||
msgstr "densità"
|
||||
|
||||
msgid "user profile"
|
||||
msgstr "profilo utente"
|
||||
|
||||
|
@ -265,6 +271,30 @@ msgstr "Sblocca"
|
|||
msgid "Custom permissions"
|
||||
msgstr "Permessi personalizzati"
|
||||
|
||||
msgid "Toggle all"
|
||||
msgstr "Attiva/disattiva tutto"
|
||||
|
||||
msgid "Toggle all add permissions"
|
||||
msgstr "Attiva/disattiva tutti i permessi di aggiunta"
|
||||
|
||||
msgid "Toggle all change permissions"
|
||||
msgstr "Attiva/disattiva tutti i permessi di modifica"
|
||||
|
||||
msgid "Toggle all delete permissions"
|
||||
msgstr "Attiva/disattiva tutti i permessi di eliminazione"
|
||||
|
||||
msgid "Toggle all publish permissions"
|
||||
msgstr "Attiva/disattiva tutti i permessi di pubblicazione"
|
||||
|
||||
msgid "Toggle all lock permissions"
|
||||
msgstr "Attiva/disattiva tutti i permessi di blocco"
|
||||
|
||||
msgid "Toggle all unlock permissions"
|
||||
msgstr "Attiva/disattiva tutti i permessi di sblocco"
|
||||
|
||||
msgid "Toggle all custom permissions"
|
||||
msgstr "Attiva/disattiva tutti i permessi personalizzati"
|
||||
|
||||
msgid "Other permissions"
|
||||
msgstr "Altri permessi"
|
||||
|
||||
|
@ -307,6 +337,9 @@ msgstr "Elimina utente"
|
|||
msgid "Select all users in listing"
|
||||
msgstr "Seleziona tutti gli utenti nella lista"
|
||||
|
||||
msgid "Sorry, no users match your query"
|
||||
msgstr "Nessun utente corrisponde alla tua ricerca"
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"There are no users configured. Why not <a "
|
||||
|
@ -395,6 +428,9 @@ msgstr "Visualizza utenti in questo gruppo"
|
|||
msgid "Group '%(object)s' deleted."
|
||||
msgstr "Gruppo '%(object)s' eliminato."
|
||||
|
||||
msgid "Last login"
|
||||
msgstr "Ultimo accesso"
|
||||
|
||||
msgid "Group"
|
||||
msgstr "Gruppo"
|
||||
|
||||
|
|
|
@ -89,6 +89,7 @@ class UserProfile(models.Model):
|
|||
SNUG = "snug", _("Snug")
|
||||
|
||||
density = models.CharField(
|
||||
# Translators: "Density" is the term used to describe the amount of space between elements in the user interface
|
||||
verbose_name=_("density"),
|
||||
choices=AdminDensityThemes.choices,
|
||||
default=AdminDensityThemes.DEFAULT,
|
||||
|
|
Ładowanie…
Reference in New Issue