Compare commits

...

104 commits

Author SHA1 Message Date
ea158ab2b5 fix(translations): update DeepL API requests to use Authorization header
Updated DeepL API requests to include the `Authorization` header with the auth key instead of passing it as a form parameter. This aligns with updated API requirements and ensures compatibility.
+ I18N
2026-03-05 16:39:59 +03:00
38903e0323 refactor(graphene): consolidate address fields into single field address_line
Replaced `address_line_1` and `address_line_2` with `address_line` to simplify the structure and reduce redundancy in the object type definition.
2026-03-05 14:14:22 +03:00
ce172721c1 chore(core): remove unused Source Code Pro ExtraLight font asset
Deleted `Source Code Pro ExtraLight_Regular.json` to clean up unused assets in the core engine. No functional changes.
2026-03-05 14:12:30 +03:00
b914b5fc91 feat(graphene): replace Float with Decimal for price and discount_price
Switched Float fields to Decimal in Graphene for increased precision in monetary values. Updated related queries and resolvers accordingly.

Additionally:
- Added custom DynamicMail health check for runtime-based email configuration.
- Enhanced admin login flow to handle unconfigured email backends by logging users directly.
2026-03-05 14:00:49 +03:00
31d9ccb82a refactor(core): simplify code for price aggregation and formatting
Improve readability by removing unnecessary parentheses in `min_price` and `max_price` methods in `models.py`. Enhance JSON serialization error formatting in `views.py` and adjustment of field formatting in serializers. No functional changes, purely stylistic updates for code maintainability.
2026-03-05 11:03:45 +03:00
6955e6bec6 feat(models/serializers): migrate to Decimal for monetary values
Replaced `float` with `Decimal` for monetary calculations to prevent precision loss and enhance accuracy. Updated related models, serializers, and utility functions accordingly.
2026-03-05 11:03:27 +03:00
e0e3c93e63 refactor(scripts): centralize static files collection in Docker entrypoint
Moved static files collection from platform-specific scripts to the Docker entrypoint script. Simplifies maintenance by eliminating redundant logic across Windows, Unix, and restart scripts.

Updated logo/icon settings to use `static()` for better flexibility.
2026-03-05 00:07:41 +03:00
3859445c78 feat(graphene): add address_line_1 and address_line_2 to object types
Expanded the object type fields to include additional address details for better data representation.
2026-03-04 20:17:30 +03:00
6ac5636779 feat(graphene): add address line fields to CreateAddress mutation
include `address_line_1` and optional `address_line_2` to enhance address creation functionality.
2026-03-04 19:59:42 +03:00
29347d1cbc chore(docker): remove unused media-data volume from services
The `media-data` volume was unused and has been removed to simplify the configuration. This helps reduce potential confusion and unused clutter in the `docker-compose.yml` file.
2026-03-03 15:18:11 +03:00
b77c2ec547 chore(docker): remove unused volumes from docker-compose.yml
Cleaned up `static-data` and `media-data` volumes as they are no longer in use, simplifying the configuration.
2026-03-03 15:10:11 +03:00
aabd8b18b4 feat(commerce): ensure precise decimal handling in aggregated calculations
replace float-based defaults with `Decimal` in Coalesce and set `DecimalField` as the output type to improve precision when aggregating monetary values.
2026-03-03 13:49:29 +03:00
e5553ac6dd refactor(vibes_auth): update admin site structure for cleaner customization
replace `extra_urls` with `get_urls` in `SchonAdminSite` for improved URL handling and compatibility with super-class methods. Simplify `apps.py` to set `SchonAdminSite` directly as the class of `admin.site`.
2026-03-03 02:40:39 +03:00
304608adc4 feat(elasticsearch): add scripts for cleaning Elasticsearch data
Introduce `delete-elasticsearch` scripts for both Windows and Unix to stop, remove containers, and delete data volumes. Updated `Makefile` to include this command for easier management. Upgraded `django-elasticsearch-dsl` and related dependencies to their latest versions for compatibility.

Breaking change: Updated Elasticsearch image and configuration to enable xpack security. Ensure environment variables are properly set.
2026-03-03 01:44:51 +03:00
ad320235d6 feat(payments, vibes_auth, core): introduce decimal fields, 2FA, and admin OTP
- Refactored monetary fields across models to use `DecimalField` for improved precision.
- Implemented two-factor authentication (2FA) for admin logins with OTP codes.
- Added ability to generate admin OTP via management commands.
- Updated Docker Compose override for dev-specific port bindings.
- Included template for 2FA OTP verification to enhance security.

Additional changes:
- Upgraded and downgraded various dependencies (e.g., django-celery-beat and yarl).
- Replaced float-based calculations with decimal for consistent rounding behavior.
- Improved admin user management commands for activation and OTP generation.
2026-03-03 00:42:21 +03:00
a5a9c70080 fix(mutations): require wishlist_uuid in mutations to ensure data integrity
Made `wishlist_uuid` a required argument to avoid processing without a valid identifier. Prevents potential errors caused by missing wishlist references.
2026-03-02 23:12:38 +03:00
dc94841f40 feat(core/blog): add support for product videos and blog post images
This commit introduces support for uploading optional video files to products and image files to blog posts. Enhanced admin interfaces were added to preview these files directly. Also includes adjustments to GraphQL types and serializers to expose the new fields.
2026-03-02 01:57:57 +03:00
d97e9a973b feat(viewsets, schema): unify retrieveExactProducts API in DRF and GraphQL
replace the custom Graphene mutation `RetrieveExactProducts` with a unified DRF implementation using `inline_serializer`. Updated GraphQL schema to resolve `retrieve_exact_products` query using a standardized approach.

This change improves consistency across DRF and GraphQL APIs, reduces duplicate logic, and centralizes request validations.
2026-03-02 01:31:41 +03:00
85576cf4ad feat(schema): add retrieveExactProducts query to support detailed product retrieval
Added the `RetrieveExactProducts` query to the schema to enable fetching specific product details. This addition enhances the granularity of product data retrieval in the GraphQL API.
2026-03-02 00:53:22 +03:00
cffbaf66b3 feat(graphql): make max query depth configurable with environment variable
allow setting `GRAPHQL_MAX_QUERY_DEPTH` via environment variable to provide flexibility in limiting query depth and preventing DoS attacks. Defaults to 13 if not set.
2026-03-02 00:45:42 +03:00
6d24ee0c92 refactor(core): use defaults in get_or_create and add annotations
Refactored `get_or_create` calls in `demo_data.py` to use the `defaults` parameter for cleaner code and consistency. Added type annotations for `get_min_price` and `get_max_price` in serializers for improved type safety.
2026-03-02 00:33:21 +03:00
c3b4637044 feat(uninstall): enhance volume removal for additional data types
add handling for postgres-data, redis-data, static-data, and media-data volumes in both Windows and Unix uninstall scripts. Ensures errors are logged if volume removal fails.
2026-03-02 00:15:36 +03:00
adec5503b2 feat(core/auth): enable encryption for sensitive fields and token handling
Add encryption for user PII fields (phone number, name, attributes) and address fields to enhance data security. Introduced timestamped activation tokens for improved validation. Included migrations to encrypt existing plaintext data.

Refactored GraphQL settings to limit query depth and optionally disable introspection for enhanced API defense. Implemented throttling to safeguard API rates.

Improved Dockerfiles for better user management and restored media migration tools for smooth instance upgrades.
2026-03-02 00:11:57 +03:00
c3d23be973 feat(dependencies): add django-fernet-encrypted-fields and update packages
Added `django-fernet-encrypted-fields` for encrypted field support in Django. Updated dependencies including `django-health-check` (4.0.6 → 4.1.0), `django-modeltranslation` (0.19.19 → 0.20.2), and `filelock` (3.24.3 → 3.25.0) to their latest versions for improved functionality and compatibility.
2026-03-01 22:58:49 +03:00
ff99177139 feat(viewsets): add endpoint to retrieve products by identifier
introduce `exact_list` action in `viewsets.py` to retrieve products by `uuid`, `slug`, or `sku` identifiers. Includes input validation and ratelimiting. Adds corresponding schema documentation and a GraphQL mutation for similar functionality.
2026-03-01 22:22:59 +03:00
ce689ee754 feat(serializers): add min_price and max_price fields in detail serializer
ensure pricing data is included in the serialized output for improved filtering capabilities.
2026-02-28 02:29:12 +03:00
eef774c3a3 feat(markdown): integrate markdown rendering and editor support
Replace WYSIWYG editor with Markdown editor across all relevant models and admin fields. Add utilities for rendering and stripping markdown. Adjust serializers, views, and templates to support markdown content. Introduce `PastedImage` model and upload endpoint for handling inline image uploads in markdown.

This change simplifies content formatting while enhancing flexibility with markdown support.
2026-02-27 23:36:51 +03:00
b6297aefa1 feat(dependencies): add django-unfold-markdown to project
add django-unfold-markdown v0.1.2 to dependencies for enhanced markdown functionality in the application. Updated `uv.lock` and `pyproject.toml` accordingly.
2026-02-27 22:35:36 +03:00
09610d98a2 feat(demo_data): enhance data generation with get_or_create
- Replaced `create` operations with `get_or_create` to ensure idempotency during data generation.
- Avoided redundant user, product image, and post creation when duplicates exist.
- Updated user and stock handling to leverage defaults for improved clarity.
- Prevented overwriting existing blog post and product image content.
2026-02-27 22:27:39 +03:00
df0d503c13 fix(demo_data): update locale override and handle missing value_ru gracefully
Adjust override to use "en-gb" for consistency with regional settings. Improve fallback behavior by assigning default value to `value_ru_ru` when `value_ru` is missing, ensuring data integrity during demo data loading.
2026-02-27 22:21:46 +03:00
0603fe320c style(models): reformat price aggregation queries for readability
Improves code readability by restructuring the `min_price` and `max_price` methods into a more concise and consistent format. No functional changes introduced.
2026-02-27 21:58:56 +03:00
7bb05d4987 feat(models): refine price aggregation to include active stocks
update `min_price` and `max_price` methods to consider only active stocks in price aggregation. This ensures more accurate price calculations by filtering out inactive stock entries.
2026-02-27 21:58:41 +03:00
b1382b09c2 chore(gitignore): fix ignore paths for production configs
Adjusted paths in `.gitignore` to correctly handle production files, ensuring alignment with expected directory structure.
2026-02-27 21:51:51 +03:00
79be6ed4e4 feat(demo_data): use translation override to ensure consistent locale
wraps actions in a `with override("en")` block to enforce the use of the English locale during execution. This ensures consistent behavior and message formatting regardless of the server's default language settings.
2026-02-27 19:02:55 +03:00
a59c5f59dd feat(blog): enhance slug behavior and expand demo data attributes
update `slug` field for `Post` model using `TweakedAutoSlugField` to improve auto-slug behavior with Unicode and additional options. Add detailed `attribute_values` to demo products for enriched metadata. Update dependencies for stability and features.
2026-02-27 18:47:14 +03:00
a1cc0cfd30 feat(category): add min_price and max_price to serializer
enable retrieval of min and max product prices for categories to support price range filters. fixed a typo in queryset filter for brands.
2026-02-27 18:05:27 +03:00
f664b088a4 refactor(category): replace cache usage with model property for min/max price
remove redundant cache lookups for `min_price` and `max_price` in the category model by leveraging cached properties. minimizes complexity and improves maintainability while ensuring consistent behavior.
2026-02-25 12:19:39 +03:00
7efc19e081 feat(monitoring): automate Prometheus web config generation
Remove manual password hashing and web.yml setup in favor of automated generation. Add scripts for both Unix and Windows to create `monitoring/web.yml` using credentials from `.env`.

This improves maintainability and reduces manual intervention during setup and configuration.
2026-02-22 00:17:55 +03:00
069d416585 refactor(monitoring): remove django-prometheus integration
Replaced `django-prometheus` with the default Django components, including model mixins, database backends, and cache configuration. This change simplifies monitoring setup by removing unnecessary dependencies, reducing overhead, and improving compatibility.

**Details:**
- Removed Prometheus metrics endpoints and middleware.
- Updated database, cache, and model configurations to remove `django-prometheus`.
- Adjusted WSGI settings to integrate OpenTelemetry instrumentation instead of Prometheus.
- Updated dependency files and migration schemas accordingly.
2026-02-21 23:44:15 +03:00
1756c3f2b2 feat(debug): integrate django-debug-toolbar for enhanced debugging
Add `django-debug-toolbar` to assist with in-depth debugging during development. Updates were made to `settings`, `urls`, and dependency files to enable this feature.
2026-02-21 22:45:04 +03:00
b6d5409fa0 fix(settings): update health check link to correct URL path
Updated the `health_check` menu link to use the correct URL path, ensuring navigation works as expected.
2026-02-21 22:33:08 +03:00
236323b93b feat(urls): add name to health_check route
Add a name to the health_check URL pattern to improve readability and enable reverse lookups.
2026-02-21 22:26:04 +03:00
8883b9f43d feat(core): replace AutoSlugField with TweakedAutoSlugField for product slugs
Updated `product.slug` to use `TweakedAutoSlugField` for improved functionality, allowing unicode, overwrite capabilities, and enhanced population logic. Adjusted the corresponding migration script to ensure seamless database schema updates.

Also marked `brand.categories` as deprecated.
2026-02-21 22:13:36 +03:00
ec167d4e9c refactor(health-check): replace default views with custom configuration
migrated health check configuration to custom settings for more precise control. Removed unused `health_check` submodules to streamline dependencies. Updated URLs to use `HealthCheckView` with tailored checks. Streamlines health monitoring and reduces unnecessary bloat.
2026-02-21 20:24:33 +03:00
72834f01f6 feat(configuration): add support for configurable language code
allow setting `SCHON_LANGUAGE_CODE` via environment files for both Windows and Unix. Default remains `en-gb`. Updated `LANGUAGE_CODE` in settings to use the new environment variable for increased flexibility.
2026-02-21 20:06:41 +03:00
0962376252 refactor(engine): downgrade ty version and clean up unused code
- Downgrade `ty` dependency from 0.0.18 to 0.0.16 in `pyproject.toml` and related files to address compatibility issues.
- Refactor `filters.py` to use safer attribute handling for field errors.
- Remove unused `TestModelDocument` and `TestModel` references from `documents.py`, reducing unnecessary overhead.
- Minor cleanup in `serializers.py` for improved readability.
2026-02-21 20:01:28 +03:00
f8f051f4e9 feat(admin-docs): add base structure and templates for admin documentation
Introduce templates for admin documentation, including model details, views, template tags, filters, and bookmarklets. This enhances the admin interface by providing detailed documentation directly within the application.
2026-02-21 19:52:28 +03:00
c728204cb1 chore(deps): update dependencies in uv.lock
upgrade versions for `aiogram` (3.24.0 → 3.25.0), `async-lru` (2.1.0 → 2.2.0), `coverage` (7.13.3 → 7.13.4), and `cryptography` (46.0.4 → 46.0.5) to incorporate latest bug fixes and enhancements.
2026-02-21 19:49:47 +03:00
87ed875fe6 feat(category): add brands relationship and resolve method
enable brands association with categories and allow querying of active brands within a category. Updated GraphQL schema, models, and serializers to include this relationship while deprecating redundant category-to-brand ManyToManyField.
2026-02-21 18:27:10 +03:00
10f5c798d4 style(demo_data): fix line break for product image save method
Simplify readability by unifying the method call into a single line. No functional changes.
2026-02-21 18:08:22 +03:00
1c10d5ca53 feat(demo_data): enhance data with images, new categories, and multilingual updates
Expanded demo content with additional images for products and blog posts, improving user experience. Added new categories such as "Jewelry" and "Services," along with their subcategories. Supported richer multilingual descriptions and included new brands, ensuring broader and detailed offerings.
2026-02-21 18:07:07 +03:00
83a8ecfcee chore(gitignore): adjust entries for queries and nginx.conf paths
Modified `.gitignore` to include relative paths for `queries` and `nginx.conf`. This ensures consistency and prevents unintended exclusions.
2026-02-17 01:09:52 +03:00
4504019100 chore(gitignore): add nginx.conf to ignored files
Add `nginx.conf` to the `.gitignore` file to prevent accidental commits of server configuration files.
2026-02-17 00:48:30 +03:00
9d5b4fee90 fix(models, viewsets): filter inactive records in queries
ensure only active records are considered in `models.py` and `viewsets.py` by adding `is_active=True` filters. improves data integrity and prevents processing inactive entities.
2026-02-16 19:43:09 +03:00
1253496e30 feat(blog): add multilingual demo blog posts showcasing Schon's features and capabilities
Includes English and Russian versions for key topics such as platform overview, bilingual experience, gemstone certification guide, holiday gift guide, and spring 2026 collection. These posts demonstrate Schon's multilingual support and flexibility in presenting rich content.
2026-02-15 12:21:39 +03:00
6311993b14 refactor(core): improve type checking and fix password handling in demo data
Replaced `pyright:ignore` with `ty:ignore` for better compatibility and accuracy in type annotations. Removed inline passwords during user creation and updated logic to securely set and save passwords afterward.
2026-02-15 03:06:17 +03:00
72a54de707 fix(demo_data): set default passwords for staff and superuser in demo data
Ensure default passwords are assigned to demo users for consistent behavior during testing and development.
2026-02-15 02:17:34 +03:00
ad5e8dc335 chore(engine): add JavaScript source map for rapidoc-min.js 2026-02-15 02:16:10 +03:00
a71e1cc29e refactor(configurations): update domain references to wiseless.xyz
Update all configurations, fixtures, scripts, and documentation to replace occurrences of `schon.fureunoir.com` with the new `schon.wiseless.xyz` domain.

This ensures consistency across the project and reflects the updated domain structure.
2026-02-15 01:39:15 +03:00
dbc41e7c53 feat(demo_data): add blog post generation to demo data script
extend the demo data management command to include blog post and tag creation. enables easier setup for testing and showcasing blog-related features.
2026-02-15 01:36:35 +03:00
1f571f294a feat(demo_data): add blog post generation to demo data script
extend the demo data management command to include blog post and tag creation. enables easier setup for testing and showcasing blog-related features.
2026-02-15 01:35:36 +03:00
30171dfbc9 chore(makefile): remove unused targets for formatting and type checking, move to pre-commit
Simplifies the Makefile by removing redundant `format`, `check`, and `typecheck` targets. Streamlines build and maintenance process.
2026-02-12 11:55:48 +03:00
20473818a9 feat(emailing): add OpenAPI schemas for unsubscribe and tracking endpoints
Includes detailed OpenAPI schemas for unsubscribe (GET and POST) and tracking pixel (GET) endpoints, supporting email compatibility and event tracking. Added support for RFC 8058-compliant one-click unsubscribe functionality and transparent image-based email tracking.
2026-02-05 19:30:53 +03:00
45a1813465 feat(admin): add demo data management command and enhance low stock UI
Introduce `demo_data` management command for populating or removing demo fixtures in the system, facilitating testing and demonstration. Enhancements include low stock product list UI with product images and integration of value formatting for financial KPIs.
2026-02-05 17:07:02 +03:00
f3260686cf feat(fixtures): add demo.json for gemstone and jewelry showcase
Includes categories, brands, attributes, products, tags, and vendor/demo user data for demonstration purposes.
2026-02-05 15:40:57 +03:00
82f4381fcb feat(core): improve XML formatting and validation rules
- update `prettify_xml` to strip XML declaration for more flexibility
- prepend XML declaration explicitly in Google Merchant feed generation
- adjust pagination `page_size` max limit to 128 for stricter validation
2026-01-30 14:43:52 +03:00
0f408fe5de fix(starter.sh): conditionally clear terminal in interactive mode
Ensure the `clear` command runs only in interactive shells with a valid terminal, preventing unintended behavior in non-interactive environments.
2026-01-30 10:29:55 +03:00
53ff3cef06 fix(Makefile): improve clear target for cross-platform compatibility
Ensure the `clear` target works on both Windows and Unix-based systems by checking the OS and terminal settings. This update avoids errors when running in unsupported environments.
2026-01-30 10:26:43 +03:00
3c652febe3 chore(core): remove obsolete price fixing command and update .gitignore
Removed the unused `fix_prices` management command to clean up legacy code. Updated `.gitignore` to include vendor documentation files, ensuring the directory is consistent in version control.
2026-01-28 21:52:57 +03:00
8245fe4d36 refactor(vendors): standardize method visibility and improve clarity
- Prefixed internal helper methods with "_" to comply with naming conventions.
- Enhanced docstrings for improved readability and developer guidance.
- Organized the `AbstractVendor` class API into distinct categories (abstract, public, and protected methods).

This refactor improves maintainability and code clarity while preserving functionality.
2026-01-28 13:46:18 +03:00
0db5b9b712 feat(vendor-management): introduce custom querysets and managers for streamlined vendor integration
Add tailored querysets and managers for `Product`, `Stock`, and `AttributeValue` models to simplify vendor-specific operations. Abstract methods were added to `AbstractVendor` for handling essential vendor processes, ensuring clear API expectations.

This change standardizes vendor-related queries, improves clarity, and ensures efficient bulk operations.
2026-01-28 13:41:38 +03:00
8d8c281eab chore(dependencies): update multidict to 6.7.1 and remove unused celery-prometheus-exporter
Upgrade `multidict` package version to 6.7.1 for compatibility and resolve potential dependency issues. Removed `celery-prometheus-exporter` as it is no longer required.
2026-01-27 20:53:30 +03:00
adfa2f20dd feat(feeds): add marketplace-specific feed generators
add feed generators for Google Merchant, Amazon Seller, Yandex Market, and Yandex Products. Includes a shared base class (`BaseFeedGenerator`) for common functionality, such as product retrieval and validation. Each generator supports format-specific output and handles platform-specific requirements.

This change simplifies the creation of product feeds for multiple marketplaces.
2026-01-26 17:44:21 +03:00
c0c1697003 refactor(locale): update Russian locale translations for accuracy
Updated translations to fix pluralization mistakes in administrative actions. Improved clarity and correctness in user-facing messages.
2026-01-26 17:06:52 +03:00
b5e303e7a5 feat(email): add multilingual support and image handling to email templates
Introduce `EmailImage`, `EmailTemplate`, and `EmailCampaign` models for enhanced email management, including campaign tracking and one-click unsubscribe tokens. Added multilingual fields for email templates and improved accessibility with image alt text. Also adapted `Post` content fields to support translations.
2026-01-26 15:35:30 +03:00
9cab9fdd3a fix(models): correct discount_price calculation logic
ensure discount_price reflects the actual discount by using the product's price. Also, suppress type-related issues in admin action descriptions for improved linting compatibility.
2026-01-26 14:51:05 +03:00
28622cec90 2026.1 2026-01-26 03:23:41 +03:00
efb56cf09b 2026.1 2026-01-25 23:18:04 +03:00
39b841fcb2 2026.1 2026-01-25 23:16:38 +03:00
8591cc93e7 Fixes: 1) Remove "author" field from general_fields in engine/blog/admin.py;
Extra: None;
2026-01-07 20:23:02 +10:00
135fc6e26d Features: 1) Add validation to return_balance_back to skip execution if status is already "RETURNED"; 2) Raise NotImplementedError when attempting to process a return without an associated user.
Fixes: None;

Extra: None;
2026-01-06 10:20:49 +03:00
afaab0354e 2026.1 2026-01-04 20:55:28 +03:00
f8b89830c5 2026.1 2026-01-04 20:47:52 +03:00
42b40627de Features: 1) Introduce CLI utility lessy.py to streamline project management tasks (e.g., install, run, restart, test); 2) Add Unix and Windows script commands for make-messages and compile-messages to improve translation workflow; 3) Include shared utility libraries (utils.sh, utils.ps1) for reusable functions across scripts.
Fixes: 1) Remove obsolete `reboot` scripts for Unix and Windows to prevent redundancy; 2) Update Windows `test.ps1` to handle omitted coverage patterns and improve error feedback.

Extra: 1) Refactor Windows scripts (`make-messages.ps1`, `compile-messages.ps1`, `backup.ps1`) to use shared utilities for better consistency and output formatting; 2) Add spinner-based progress indicators to enhance user experience in interactive environments.
2026-01-04 19:18:27 +03:00
4c7b40b899 Features: 1) Add orjson serializer and integrate it into Celery configuration for enhanced performance and compatibility; 2) Introduce configurable task settings such as soft and hard time limits, task tracking, and event serialization for better control and observability.
Fixes: 1) Update worker entrypoints to adjust prefetch multiplier and memory/task limits for optimized resource usage.

Extra: 1) Refactor Celery settings into a dedicated file for improved organization and maintainability; 2) Adjust Docker entrypoints to align with updated task configurations; 3) Register `orjson` serializer in a separate module for cleaner code structure.
2026-01-04 18:37:00 +03:00
4e89b2aeec Fixes: 1) Correct improper function scope for user.save in UpdateUser mutation;
Extra: 1) Minor code readability adjustment.
2025-12-29 17:36:17 +03:00
a91f8e30d3 Features: 1) Add value_type field to FilterableAttributeType for enhanced attribute customization;
Fixes: 1) Remove unused import of `i18n_patterns` in `urls.py`; 2) Update `coverage`, `psutil`, and `types-psutil` dependencies to latest stable versions in `pyproject.toml` and lock file for improved package compatibility;

Extra: 1) General housekeeping and dependency synchronization in `pyproject.toml` and lock file.
2025-12-29 16:04:41 +03:00
28f6e4d78f - Enhance maintenance page: add dynamic theme support, multilingual support, and improved 3D text rendering logic.
- Simplify URL configuration by replacing i18n patterns with direct path handling.
2025-12-28 17:05:20 +03:00
646fc5e1d7 Refactor attribute handling in vendor module: update type annotations and simplify get_or_create logic. 2025-12-27 01:08:56 +03:00
b6d38d07e5 - Improve pagination logic: add default and validation for page size, fix get_paginated_response behavior.
- Update type annotations in `check_translated` command for app config handling.
- Pin and upgrade dependencies in `pyproject.toml` for consistency and latest features.
2025-12-25 14:50:37 +03:00
e9219c8918 Fixes: 1) Correct invalid return type in get_children; 2) Update default return value from {} to [];
Extra: 1) Adjust inline comments for better clarity; 2) General cleanup in `simple.py`.
2025-12-22 13:53:04 +03:00
e312cb8a71 Features: DRF docs I18N 2025-12-21 02:12:01 +03:00
95fd17bb43 Features: DRF docs I18N 2025-12-21 02:11:51 +03:00
29fb56be89 Features: 1) Add async and sync capabilities to CamelCaseMiddleWare; 2) Include OpenAPI support for Enum name overrides in DRF settings; 3) Integrate OpenAPI types in DRF views for improved schema accuracy.
Fixes: 1) Correct `lookup_field` to `uuid` in various viewsets; 2) Replace `type=str` with `OpenApiTypes.STR` in path parameters of multiple DRF endpoints; 3) Add missing import `iscoroutinefunction` and `markcoroutinefunction`.

Extra: 1) Refactor `__call__` method in `CamelCaseMiddleWare` to separate sync and async logic; 2) Enhance documentation schema responses with precise types in multiple DRF views.
2025-12-19 17:27:36 +03:00
dc7f8be926 Features: 1) None;
Fixes: 1) Add `# ty: ignore` comments to suppress type errors in multiple files; 2) Correct method argument annotations and definitions to align with type hints; 3) Fix cases of invalid or missing imports and unresolved attributes;

Extra: Refactor method definitions to use tuple-based method declarations; replace custom type aliases with `Any`; improve caching utility and error handling logic in utility scripts.
2025-12-19 16:43:39 +03:00
13e7af52aa Features: 1) Improved request processing in middleware by adding mutable QueryDict implementation; 2) Extended type annotations across various modules for enhanced type safety; 3) Refined JWT token lifetime configuration for environment-specific logic.
Fixes: 1) Addressed missing or incorrect imports and type hints with `# ty:ignore` markers; 2) Fixed search queryset error handling in filters module; 3) Resolved issues in viewsets with updated `@action` method usage.

Extra: Removed unused classes and dependencies (e.g., `BaseMutation`, `basedpyright`, and related packages); streamlined GraphQL mutation implementations; cleaned up unused arguments in model `save` methods.
2025-12-19 15:17:17 +03:00
c3b4becc76 Features: 1) Add "is_updatable" to list_filter and additional_fields in admin configuration;
Fixes: None;

Extra: None;
2025-12-18 21:21:28 +03:00
e852d6adf2 Features: 1) Add is_updatable field to Product model with migration; 2) Introduce check_updatable method in AbstractVendor to validate product updatability; 3) Define ProductUnapdatableError for handling non-updatable product logic;
Fixes: none;

Extra: none;
2025-12-18 21:19:01 +03:00
160b35a591 Features: (1) Update camelize function to separately handle lists and tuples for improved clarity;
Fixes: (1) Correct development server URL in DRF settings from `http://api.localhost:8000/` to `http://localhost:8000/`;

Extra: (1) Minor alignment adjustments in `renderers.py` for better readability.
2025-12-18 19:36:05 +03:00
fdd42b8531 Features: I thought that's gonna be like 2 weeks stuff 2025-12-18 19:24:10 +03:00
1e06aace46 Revert .mo replaces 2025-12-18 16:52:03 +03:00
5f5274f9cd Features: 1) Add detailed type annotations across middleware, tests, and utility code; 2) Integrate stricter type-checking configurations in pyproject.toml; 3) Enhance middleware functionality with additional type-safe logic.
Fixes: 1) Correct default values and type handling in util constructors; 2) Resolve missing or ambiguous `cast` operations for dynamic typing in tests and views; 3) Address potential issues with fallback/default handling in middleware.

Extra: 1) Refactor test cases to ensure stricter adherence to typing hints and valid contracts; 2) Update docstrings to align with new type annotations; 3) Cleanup unused imports and add comments for improved maintainability.
2025-12-18 16:44:13 +03:00
a81f734e23 Features: (1) None;
Fixes: (1) Removed all `# type: ignore` annotations across the codebase; (2) Fixed usage of Django Model methods by eliminating unnecessary `# type: ignore` directives; (3) Adjusted usage of functions like `get()` to align with method expectations, removing incorrect comments;

Extra: (1) Deleted `pyrightconfig.json` as part of migration to a stricter type-checked environment; (2) Minor code cleanup, including formatting changes and refactoring import statements in adherence to PEP8 recommendations.
2025-12-18 15:55:43 +03:00
dd8652df96 Features: 1) Update vendor object creation to use fully qualified module path;
Fixes: 1) Correct path for vendor module resolving import issue;

Extra: 1) Improve readability of vendor initialization logic;
2025-12-17 16:02:35 +03:00
632 changed files with 75240 additions and 30948 deletions

237
.gitignore vendored
View file

@ -1,137 +1,150 @@
# ────────────────────────────────────────────────────────────────────────── # ╔═══════════════════════════════════════════════════════════════════════════╗
# Python bytecode, caches, and compiled artifacts # ║ ║
# ────────────────────────────────────────────────────────────────────────── # ║ ███████╗ ██████╗██╗ ██╗ ██████╗ ███╗ ██╗ ║
# Byte-compiled / optimized / DLL files # ║ ██╔════╝██╔════╝██║ ██║██╔═══██╗████╗ ██║ ║
# ║ ███████╗██║ ███████║██║ ██║██╔██╗ ██║ ║
# ║ ╚════██║██║ ██╔══██║██║ ██║██║╚██╗██║ ║
# ║ ███████║╚██████╗██║ ██║╚██████╔╝██║ ╚████║ ║
# ║ ╚══════╝ ╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ║
# ║ ║
# ╚═══════════════════════════════════════════════════════════════════════════╝
# ┌───────────────────────────────────────────────────────────────────────────┐
# │ Python │
# └───────────────────────────────────────────────────────────────────────────┘
# Bytecode & compiled
*.py[cod] *.py[cod]
*$py.class *$py.class
*.so *.so
# Cache directories # Caches
__pycache__/ __pycache__/
.pytest_cache/ .pytest_cache/
.hypothesis/ .hypothesis/
.mypy_cache/
.pyre/ .pyre/
.pytype/ .pytype/
.mypy_cache/
.dmypy.json .dmypy.json
dmypy.json dmypy.json
cython_debug/ cython_debug/
# Python environments # Virtual environments
.Python .Python
.venv/
venv/
env/ env/
ENV/
env.bak/ env.bak/
ENV.bak/ ENV.bak/
venv/
venv.bak/ venv.bak/
ENV/
.venv/
__pypackages__/ __pypackages__/
.python-version .python-version
# Local Django settings and database
# ┌───────────────────────────────────────────────────────────────────────────┐
# │ Django │
# └───────────────────────────────────────────────────────────────────────────┘
local_settings.py local_settings.py
db.sqlite3 db.sqlite3
db.sqlite3-journal db.sqlite3-journal
# Django backups and metadata
instance/ instance/
backups/ backups/
# ────────────────────────────────────────────────────────────────────────── # Static & media (collected)
# Logs and reports
# ──────────────────────────────────────────────────────────────────────────
logs/
*.log
debug.log
errors.log
test.json
coverage.xml
coverage.*
*.cover
*.py,cover
nosetests.xml
tmp
# Coverage / test reports
htmlcov/
.coverage
.coverage.*
# CI / tox / nox
.tox/
.nox/
.scrapy
.cover
.pybuilder/
# Storefronts
.nuxt/
.next/
next-env.d.ts
# Celery
celerybeat-schedule
celerybeat.pid
# ──────────────────────────────────────────────────────────────────────────
# Packaging and distribution
# ──────────────────────────────────────────────────────────────────────────
build/
dist/
dist-ssr/
*.egg
*.egg-info/
.installed.cfg
MANIFEST
*.manifest
*.spec
sdist/
wheels/
share/python-wheels/
pip-log.txt
pip-delete-this-directory.txt
desktop.ini
*.iml
# Node build artifacts
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
site/
# ──────────────────────────────────────────────────────────────────────────
# Static, media, and uploads
# ──────────────────────────────────────────────────────────────────────────
static/ static/
media/ media/
# Allow checked-in static from apps # Allow checked-in app static files
!engine/core/static/ !engine/core/static/
!engine/payments/static/ !engine/payments/static/
!engine/vibes_auth/static/ !engine/vibes_auth/static/
!engine/blog/static/ !engine/blog/static/
# Webassets # Celery
.webassets-cache/ celerybeat-schedule
celerybeat.pid
# ┌───────────────────────────────────────────────────────────────────────────┐
# │ Testing & Coverage │
# └───────────────────────────────────────────────────────────────────────────┘
htmlcov/
.coverage
.coverage.*
coverage.xml
coverage.*
*.cover
*.py,cover
nosetests.xml
test.json
.tox/
.nox/
# ┌───────────────────────────────────────────────────────────────────────────┐
# │ Logs │
# └───────────────────────────────────────────────────────────────────────────┘
logs/
*.log
debug.log
errors.log
tmp/
# ┌───────────────────────────────────────────────────────────────────────────┐
# │ Build & Distribution │
# └───────────────────────────────────────────────────────────────────────────┘
build/
dist/
dist-ssr/
sdist/
wheels/
*.egg
*.egg-info/
*.manifest
*.spec
.installed.cfg
MANIFEST
share/python-wheels/
pip-log.txt
pip-delete-this-directory.txt
# ┌───────────────────────────────────────────────────────────────────────────┐
# │ Node.js & Frontend │
# └───────────────────────────────────────────────────────────────────────────┘
# ──────────────────────────────────────────────────────────────────────────
# Node dependencies
# ──────────────────────────────────────────────────────────────────────────
node_modules/ node_modules/
.nuxt/
.next/
next-env.d.ts
*.tsbuildinfo
.webassets-cache/
site/
# ────────────────────────────────────────────────────────────────────────── # Debug logs
# Cypress test artifacts npm-debug.log*
# ────────────────────────────────────────────────────────────────────────── yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Cypress
cypress/videos/ cypress/videos/
cypress/screenshots/ cypress/screenshots/
# ──────────────────────────────────────────────────────────────────────────
# IDEs and editors # ┌───────────────────────────────────────────────────────────────────────────┐
# ────────────────────────────────────────────────────────────────────────── # │ IDEs & Editors │
# VSCode # └───────────────────────────────────────────────────────────────────────────┘
# VS Code
.vscode/ .vscode/
!.vscode/extensions.json !.vscode/extensions.json
@ -139,33 +152,49 @@ cypress/screenshots/
.idea/ .idea/
!.idea/icon.svg !.idea/icon.svg
!.idea/externalDependencies.xml !.idea/externalDependencies.xml
!.idea/evibes.iml !.idea/schon.iml
!.idea/evibes.ico
# Microsoft # Visual Studio
*.suo *.suo
*.ntvs* *.ntvs*
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
# Spyder / Rope # Spyder & Rope
.spyderproject .spyderproject
.spyproject .spyproject
.ropeproject .ropeproject
# macOS
# ┌───────────────────────────────────────────────────────────────────────────┐
# │ OS & System │
# └───────────────────────────────────────────────────────────────────────────┘
.DS_Store .DS_Store
desktop.ini
*.iml
# TypeScript
*.tsbuildinfo
# Environment file # ┌───────────────────────────────────────────────────────────────────────────┐
# │ Project-specific │
# └───────────────────────────────────────────────────────────────────────────┘
# Environment
.env .env
# Development stuff # Development
test.ipynb test.ipynb
.scrapy
.cover
.pybuilder/
engine/core/vendors/docs/*
# Production stuff # Production
.initialized .initialized
queries /queries
/nginx.conf
/monitoring/web.yml
# AI assistants
.claude/

View file

@ -1,53 +0,0 @@
image: ghcr.io/astral-sh/uv:python3.12-bookworm
stages:
- lint
- typecheck
- test
variables:
UV_PYTHON: "3.12"
PIP_DISABLE_PIP_VERSION_CHECK: "1"
PYTHONDONTWRITEBYTECODE: "1"
before_script:
- uv sync --frozen --extra linting
lint:
stage: lint
script:
- uv run ruff format --check .
- uv run ruff check --force-exclude .
rules:
- changes:
- "**/*.py"
- "pyproject.toml"
- ".pre-commit-config.yaml"
- "pyrightconfig.json"
when: on_success
- when: never
typecheck:
stage: typecheck
script:
- uv run pyright
rules:
- changes:
- "**/*.py"
- "pyproject.toml"
- "pyrightconfig.json"
when: on_success
- when: never
test:
stage: test
script:
- uv run pytest -q
rules:
- changes:
- "**/*.py"
- "pyproject.toml"
- "pytest.ini"
- "pyproject.toml"
when: on_success
- when: never

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 355 KiB

View file

@ -1,18 +0,0 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.9
hooks:
- id: ruff
name: Ruff (lint & fix)
args: ["--fix", "--exit-non-zero-on-fix"]
files: "\\.(py|pyi)$"
exclude: "^storefront/"
- id: ruff-format
name: Ruff (format)
files: "\\.(py|pyi)$"
exclude: "^storefront/"
ci:
autofix_commit_msg: "chore(pre-commit): auto-fix issues"
autofix_prs: true
autoupdate_commit_msg: "chore(pre-commit): autoupdate hooks"

35
CHANGELOG.md Normal file
View file

@ -0,0 +1,35 @@
# Changelog
All notable changes to Schon are documented in this file.
Version format follows [CalVer](https://calver.org/) as `YYYY.MAJOR`.
---
## [2026.1] - 2026-01-25
Initial CalVer release. Project renamed from eVibes to Schon.
### Highlights
- Complete rebrand from eVibes to Schon
- Native Linux deployment with systemd service files
- uv-based dependency management
- Interactive installer supporting Docker and native deployments
- Professional documentation and project structure
### Added
- systemd service files for production deployment (`schon-web`, `schon-worker`, `schon-beat`, `schon-stock-updater`)
- Native Linux installation script with automated setup
- CHANGELOG.md for tracking releases
### Changed
- Project name: eVibes to Schon
- Version scheme: SemVer to CalVer (YYYY.MAJOR)
- Installation: `make install` now prompts for Docker vs native deployment
### Removed
- Legacy eVibes branding and assets

View file

@ -26,7 +26,7 @@ uv.lock @fureunoir contact@fureunoir.com
blog/ @fureunoir contact@fureunoir.com blog/ @fureunoir contact@fureunoir.com
core/ @fureunoir contact@fureunoir.com core/ @fureunoir contact@fureunoir.com
evibes/ @fureunoir contact@fureunoir.com schon/ @fureunoir contact@fureunoir.com
payments/ @fureunoir contact@fureunoir.com payments/ @fureunoir contact@fureunoir.com
scripts/ @fureunoir contact@fureunoir.com scripts/ @fureunoir contact@fureunoir.com
vibes_auth/ @fureunoir contact@fureunoir.com vibes_auth/ @fureunoir contact@fureunoir.com

View file

@ -5,8 +5,7 @@ LABEL authors="fureunoir"
ENV PYTHONDONTWRITEBYTECODE=1 \ ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \ PYTHONUNBUFFERED=1 \
LANG=C.UTF-8 \ LANG=C.UTF-8 \
DEBIAN_FRONTEND=noninteractive \ DEBIAN_FRONTEND=noninteractive
PATH="/root/.local/bin:$PATH"
WORKDIR /app WORKDIR /app
@ -23,11 +22,9 @@ RUN set -eux; \
libpq-dev \ libpq-dev \
gettext \ gettext \
libgettextpo-dev \ libgettextpo-dev \
graphviz-dev \
libgts-dev \ libgts-dev \
libpq5 \ libpq5 \
chrony \ chrony \
graphviz \
binutils \ binutils \
libproj-dev \ libproj-dev \
postgresql-client-17 \ postgresql-client-17 \
@ -35,23 +32,28 @@ RUN set -eux; \
rm -rf /var/lib/apt/lists/*; \ rm -rf /var/lib/apt/lists/*; \
pip install --upgrade pip pip install --upgrade pip
RUN curl -LsSf https://astral.sh/uv/install.sh | sh RUN curl -LsSf https://astral.sh/uv/install.sh | UV_INSTALL_DIR=/usr/local/bin sh
ENV PATH="/root/.local/bin:/root/.cargo/bin:$PATH"
RUN uv venv /opt/evibes-python ENV VIRTUAL_ENV=/opt/schon-python
ENV VIRTUAL_ENV=/opt/evibes-python ENV UV_PROJECT_ENVIRONMENT=/opt/schon-python
ENV UV_PROJECT_ENVIRONMENT=/opt/evibes-python ENV PATH="/opt/schon-python/bin:/usr/local/bin:$PATH"
ENV PATH="/opt/evibes-python/bin:/root/.local/bin:/root/.cargo/bin:$PATH"
COPY pyproject.toml pyproject.toml COPY pyproject.toml pyproject.toml
COPY uv.lock uv.lock COPY uv.lock uv.lock
RUN set -eux; \ RUN uv venv /opt/schon-python && \
uv sync --extra graph --extra worker --extra openai --locked uv sync --extra worker --extra openai --locked
COPY ./scripts/Docker/app-entrypoint.sh /usr/local/bin/app-entrypoint.sh COPY ./scripts/Docker/app-entrypoint.sh /usr/local/bin/app-entrypoint.sh
RUN chmod +x /usr/local/bin/app-entrypoint.sh RUN chmod +x /usr/local/bin/app-entrypoint.sh
COPY . . COPY . .
RUN groupadd --system --gid 1000 schon && \
useradd --system --uid 1000 --gid schon --shell /bin/bash --create-home schon && \
mkdir -p /app/static /app/media && \
chown -R schon:schon /app /opt/schon-python
USER schon
ENTRYPOINT ["/usr/bin/bash", "app-entrypoint.sh"] ENTRYPOINT ["/usr/bin/bash", "app-entrypoint.sh"]

View file

@ -5,8 +5,7 @@ LABEL authors="fureunoir"
ENV PYTHONDONTWRITEBYTECODE=1 \ ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \ PYTHONUNBUFFERED=1 \
LANG=C.UTF-8 \ LANG=C.UTF-8 \
DEBIAN_FRONTEND=noninteractive \ DEBIAN_FRONTEND=noninteractive
PATH="/root/.local/bin:$PATH"
WORKDIR /app WORKDIR /app
@ -23,11 +22,9 @@ RUN set -eux; \
libpq-dev \ libpq-dev \
gettext \ gettext \
libgettextpo-dev \ libgettextpo-dev \
graphviz-dev \
libgts-dev \ libgts-dev \
libpq5 \ libpq5 \
chrony \ chrony \
graphviz \
binutils \ binutils \
libproj-dev \ libproj-dev \
postgresql-client-17 \ postgresql-client-17 \
@ -35,18 +32,16 @@ RUN set -eux; \
rm -rf /var/lib/apt/lists/*; \ rm -rf /var/lib/apt/lists/*; \
pip install --upgrade pip pip install --upgrade pip
RUN curl -LsSf https://astral.sh/uv/install.sh | sh RUN curl -LsSf https://astral.sh/uv/install.sh | UV_INSTALL_DIR=/usr/local/bin sh
ENV PATH="/root/.local/bin:/root/.cargo/bin:$PATH"
ENV UV_PROJECT_ENVIRONMENT=/opt/evibes-python
ENV PATH="/opt/evibes-python/bin:/root/.local/bin:/root/.cargo/bin:$PATH"
RUN uv venv /opt/evibes-python ENV VIRTUAL_ENV=/opt/schon-python
ENV VIRTUAL_ENV=/opt/evibes-python ENV UV_PROJECT_ENVIRONMENT=/opt/schon-python
ENV PATH="/opt/schon-python/bin:/usr/local/bin:$PATH"
COPY pyproject.toml pyproject.toml COPY pyproject.toml pyproject.toml
COPY uv.lock uv.lock COPY uv.lock uv.lock
RUN set -eux; \ RUN uv venv /opt/schon-python && \
uv sync --extra worker --extra openai --locked uv sync --extra worker --extra openai --locked
COPY ./scripts/Docker/beat-entrypoint.sh /usr/local/bin/beat-entrypoint.sh COPY ./scripts/Docker/beat-entrypoint.sh /usr/local/bin/beat-entrypoint.sh
@ -54,4 +49,11 @@ RUN chmod +x /usr/local/bin/beat-entrypoint.sh
COPY . . COPY . .
RUN groupadd --system --gid 1000 schon && \
useradd --system --uid 1000 --gid schon --shell /bin/bash --create-home schon && \
mkdir -p /app/media && \
chown -R schon:schon /app /opt/schon-python
USER schon
ENTRYPOINT ["/usr/bin/bash", "beat-entrypoint.sh"] ENTRYPOINT ["/usr/bin/bash", "beat-entrypoint.sh"]

View file

@ -5,8 +5,7 @@ LABEL authors="fureunoir"
ENV PYTHONDONTWRITEBYTECODE=1 \ ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \ PYTHONUNBUFFERED=1 \
LANG=C.UTF-8 \ LANG=C.UTF-8 \
DEBIAN_FRONTEND=noninteractive \ DEBIAN_FRONTEND=noninteractive
PATH="/root/.local/bin:$PATH"
WORKDIR /app WORKDIR /app
@ -23,11 +22,9 @@ RUN set -eux; \
libpq-dev \ libpq-dev \
gettext \ gettext \
libgettextpo-dev \ libgettextpo-dev \
graphviz-dev \
libgts-dev \ libgts-dev \
libpq5 \ libpq5 \
chrony \ chrony \
graphviz \
binutils \ binutils \
libproj-dev \ libproj-dev \
postgresql-client-17 \ postgresql-client-17 \
@ -35,18 +32,16 @@ RUN set -eux; \
rm -rf /var/lib/apt/lists/*; \ rm -rf /var/lib/apt/lists/*; \
pip install --upgrade pip pip install --upgrade pip
RUN curl -LsSf https://astral.sh/uv/install.sh | sh RUN curl -LsSf https://astral.sh/uv/install.sh | UV_INSTALL_DIR=/usr/local/bin sh
ENV PATH="/root/.local/bin:/root/.cargo/bin:$PATH"
ENV UV_PROJECT_ENVIRONMENT=/opt/evibes-python
ENV PATH="/opt/evibes-python/bin:/root/.local/bin:/root/.cargo/bin:$PATH"
RUN uv venv /opt/evibes-python ENV VIRTUAL_ENV=/opt/schon-python
ENV VIRTUAL_ENV=/opt/evibes-python ENV UV_PROJECT_ENVIRONMENT=/opt/schon-python
ENV PATH="/opt/schon-python/bin:/usr/local/bin:$PATH"
COPY pyproject.toml pyproject.toml COPY pyproject.toml pyproject.toml
COPY uv.lock uv.lock COPY uv.lock uv.lock
RUN set -eux; \ RUN uv venv /opt/schon-python && \
uv sync --extra worker --extra openai --locked uv sync --extra worker --extra openai --locked
COPY ./scripts/Docker/stock-updater-entrypoint.sh /usr/local/bin/stock-updater-entrypoint.sh COPY ./scripts/Docker/stock-updater-entrypoint.sh /usr/local/bin/stock-updater-entrypoint.sh
@ -54,4 +49,11 @@ RUN chmod +x /usr/local/bin/stock-updater-entrypoint.sh
COPY . . COPY . .
RUN groupadd --system --gid 1000 schon && \
useradd --system --uid 1000 --gid schon --shell /bin/bash --create-home schon && \
mkdir -p /app/media && \
chown -R schon:schon /app /opt/schon-python
USER schon
ENTRYPOINT ["/usr/bin/bash", "stock-updater-entrypoint.sh"] ENTRYPOINT ["/usr/bin/bash", "stock-updater-entrypoint.sh"]

View file

@ -2,10 +2,10 @@
FROM node:22-bookworm-slim AS build FROM node:22-bookworm-slim AS build
WORKDIR /app WORKDIR /app
ARG EVIBES_BASE_DOMAIN ARG schon_BASE_DOMAIN
ARG EVIBES_PROJECT_NAME ARG schon_PROJECT_NAME
ENV EVIBES_BASE_DOMAIN=$EVIBES_BASE_DOMAIN ENV schon_BASE_DOMAIN=$schon_BASE_DOMAIN
ENV EVIBES_PROJECT_NAME=$EVIBES_PROJECT_NAME ENV schon_PROJECT_NAME=$schon_PROJECT_NAME
COPY ./supervisor/package.json ./supervisor/package-lock.json ./ COPY ./supervisor/package.json ./supervisor/package-lock.json ./
RUN npm ci --include=optional RUN npm ci --include=optional

View file

@ -5,8 +5,7 @@ LABEL authors="fureunoir"
ENV PYTHONDONTWRITEBYTECODE=1 \ ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \ PYTHONUNBUFFERED=1 \
LANG=C.UTF-8 \ LANG=C.UTF-8 \
DEBIAN_FRONTEND=noninteractive \ DEBIAN_FRONTEND=noninteractive
PATH="/root/.local/bin:$PATH"
WORKDIR /app WORKDIR /app
@ -23,11 +22,9 @@ RUN set -eux; \
libpq-dev \ libpq-dev \
gettext \ gettext \
libgettextpo-dev \ libgettextpo-dev \
graphviz-dev \
libgts-dev \ libgts-dev \
libpq5 \ libpq5 \
chrony \ chrony \
graphviz \
binutils \ binutils \
libproj-dev \ libproj-dev \
postgresql-client-17 \ postgresql-client-17 \
@ -35,18 +32,16 @@ RUN set -eux; \
rm -rf /var/lib/apt/lists/*; \ rm -rf /var/lib/apt/lists/*; \
pip install --upgrade pip pip install --upgrade pip
RUN curl -LsSf https://astral.sh/uv/install.sh | sh RUN curl -LsSf https://astral.sh/uv/install.sh | UV_INSTALL_DIR=/usr/local/bin sh
ENV PATH="/root/.local/bin:/root/.cargo/bin:$PATH"
RUN uv venv /opt/evibes-python ENV VIRTUAL_ENV=/opt/schon-python
ENV VIRTUAL_ENV=/opt/evibes-python ENV UV_PROJECT_ENVIRONMENT=/opt/schon-python
ENV UV_PROJECT_ENVIRONMENT=/opt/evibes-python ENV PATH="/opt/schon-python/bin:/usr/local/bin:$PATH"
ENV PATH="/opt/evibes-python/bin:/root/.local/bin:/root/.cargo/bin:$PATH"
COPY pyproject.toml pyproject.toml COPY pyproject.toml pyproject.toml
COPY uv.lock uv.lock COPY uv.lock uv.lock
RUN set -eux; \ RUN uv venv /opt/schon-python && \
uv sync --extra worker --extra openai --locked uv sync --extra worker --extra openai --locked
COPY ./scripts/Docker/worker-entrypoint.sh /usr/local/bin/worker-entrypoint.sh COPY ./scripts/Docker/worker-entrypoint.sh /usr/local/bin/worker-entrypoint.sh
@ -54,4 +49,11 @@ RUN chmod +x /usr/local/bin/worker-entrypoint.sh
COPY . . COPY . .
RUN groupadd --system --gid 1000 schon && \
useradd --system --uid 1000 --gid schon --shell /bin/bash --create-home schon && \
mkdir -p /app/media && \
chown -R schon:schon /app /opt/schon-python
USER schon
ENTRYPOINT ["/usr/bin/bash", "worker-entrypoint.sh"] ENTRYPOINT ["/usr/bin/bash", "worker-entrypoint.sh"]

View file

@ -1,8 +1,8 @@
eVibes License Version 2.0, April 29, 2025 Schon License Version 2.0, April 29, 2025
Copyright (c) 2025 Egor “fureunoir” Gorbunov Copyright (c) 2025 Egor “fureunoir” Gorbunov
Permission is hereby granted, free of charge, to any person obtaining a copy of the eVibes software and associated documentation (the “Software”), to use, copy, modify, merge, publish, distribute, and sublicense the Software, subject to the terms and conditions below. Any distribution of the Software (in source or binary form) must include a copy of this License and preserve the above copyright notice. By using the Software, you indicate your acceptance of these terms. If you do not agree to these terms, you have no rights to use the Software. Permission is hereby granted, free of charge, to any person obtaining a copy of the Schon software and associated documentation (the “Software”), to use, copy, modify, merge, publish, distribute, and sublicense the Software, subject to the terms and conditions below. Any distribution of the Software (in source or binary form) must include a copy of this License and preserve the above copyright notice. By using the Software, you indicate your acceptance of these terms. If you do not agree to these terms, you have no rights to use the Software.
1. Non-Commercial Use 1. Non-Commercial Use
The Software is provided at no cost for personal, academic, or other non-commercial purposes. “Non-Commercial Use” means any use of the Software that does not generate income (directly or indirectly) and is not part of a for-profit or revenue-generating activity. For Non-Commercial Use, the Software is provided “AS IS” and without any warranty or liability. You may freely use and modify the Software for Non-Commercial purposes, and you may distribute it for non-commercial ends as long as this License is included and the same terms apply to all recipients. The Software is provided at no cost for personal, academic, or other non-commercial purposes. “Non-Commercial Use” means any use of the Software that does not generate income (directly or indirectly) and is not part of a for-profit or revenue-generating activity. For Non-Commercial Use, the Software is provided “AS IS” and without any warranty or liability. You may freely use and modify the Software for Non-Commercial purposes, and you may distribute it for non-commercial ends as long as this License is included and the same terms apply to all recipients.
@ -55,4 +55,4 @@ Egor “fureunoir” Gorbunov
Email: contact@fureunoir.com Email: contact@fureunoir.com
Telegram: https://t.me/fureunoir Telegram: https://t.me/fureunoir
By using the eVibes framework, you acknowledge that you have read and understood this License and agree to be bound by its terms. By using the Schon framework, you acknowledge that you have read and understood this License and agree to be bound by its terms.

113
Makefile Normal file
View file

@ -0,0 +1,113 @@
.PHONY: help install run restart test test-xml test-html uninstall backup \
generate-env export-env make-messages compile-messages \
format check typecheck precommit clear make-migrations migrate \
delete-elasticsearch
# Detect OS and set script paths
ifeq ($(OS),Windows_NT)
SCRIPT_DIR := scripts/Windows
SCRIPT_EXT := .ps1
RUN_SCRIPT = pwsh -ExecutionPolicy Bypass -File ./$(SCRIPT_DIR)/$(1)$(SCRIPT_EXT)
CLEAR_CMD := cls
else
SCRIPT_DIR := scripts/Unix
SCRIPT_EXT := .sh
RUN_SCRIPT = bash ./$(SCRIPT_DIR)/$(1)$(SCRIPT_EXT)
CLEAR_CMD := clear
endif
clear:
ifeq ($(OS),Windows_NT)
@$(CLEAR_CMD)
else
@if [ -t 1 ] && [ -n "$$TERM" ]; then $(CLEAR_CMD); fi 2>/dev/null || true
endif
help: clear
@echo "Schon Project Management"
@echo ""
@echo "Usage: make [target]"
@echo ""
@echo "Targets:"
@echo " install Pull and build Docker images"
@echo " run Start all services"
@echo " restart Restart all services"
@echo " test Run tests with coverage"
@echo " test-xml Generate XML coverage report"
@echo " test-html Generate HTML coverage report"
@echo " uninstall Remove containers, volumes, and generated files"
@echo " delete-elasticsearch Wipe Elasticsearch data (recreated on restart)"
@echo " backup Create a backup"
@echo " generate-env Generate .env file from template"
@echo " export-env Export environment variables"
@echo " make-messages Extract translation strings"
@echo " compile-messages Compile translation files"
@echo " make-migrations Generate migration files"
@echo " migrate Apply migration files"
@echo " format Format code with ruff"
@echo " check Lint code with ruff"
@echo " typecheck Typecheck code with ty"
@echo " precommit Run format, check, and typecheck"
@echo ""
@echo "Detected OS: $(if $(filter Windows_NT,$(OS)),Windows,Unix)"
@echo "Scripts directory: $(SCRIPT_DIR)"
install: clear
@$(call RUN_SCRIPT,install)
run: clear
@$(call RUN_SCRIPT,run)
restart: clear
@$(call RUN_SCRIPT,restart)
test: clear
@$(call RUN_SCRIPT,test)
test-xml: clear
ifeq ($(OS),Windows_NT)
@pwsh -ExecutionPolicy Bypass -File ./$(SCRIPT_DIR)/test$(SCRIPT_EXT) -r xml
else
@bash ./$(SCRIPT_DIR)/test$(SCRIPT_EXT) --report xml
endif
test-html: clear
ifeq ($(OS),Windows_NT)
@pwsh -ExecutionPolicy Bypass -File ./$(SCRIPT_DIR)/test$(SCRIPT_EXT) -r html
else
@bash ./$(SCRIPT_DIR)/test$(SCRIPT_EXT) --report html
endif
uninstall: clear
@echo "This will remove all Docker containers, volumes, and generated files."
ifeq ($(OS),Windows_NT)
@pwsh -Command "$$confirm = Read-Host 'Continue? [y/N]'; if ($$confirm -eq 'y') { pwsh -ExecutionPolicy Bypass -File ./$(SCRIPT_DIR)/uninstall$(SCRIPT_EXT) } else { Write-Host 'Uninstall cancelled.' }"
else
@read -p "Continue? [y/N] " confirm && [ "$$confirm" = "y" ] && bash ./$(SCRIPT_DIR)/uninstall$(SCRIPT_EXT) || echo "Uninstall cancelled."
endif
backup: clear
@$(call RUN_SCRIPT,backup)
generate-env: clear
@$(call RUN_SCRIPT,generate-environment-file)
export-env: clear
@$(call RUN_SCRIPT,export-environment-file)
make-messages: clear
@$(call RUN_SCRIPT,make-messages)
compile-messages: clear
@$(call RUN_SCRIPT,compile-messages)
make-migrations: clear
@$(call RUN_SCRIPT,make-migrations)
migrate: clear
@$(call RUN_SCRIPT,migrate)
migration: clear make-migrations migrate
delete-elasticsearch: clear
@$(call RUN_SCRIPT,delete-elasticsearch)

321
README.md
View file

@ -1,132 +1,263 @@
# eVibes # Schon
![LOGO](engine/core/docs/images/evibes-big.png) ![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)
![Django 5.2](https://img.shields.io/badge/django-5.2-green.svg)
![License](https://img.shields.io/badge/license-Custom-red.svg)
![Version](https://img.shields.io/badge/version-2026.1-purple.svg)
eVibes — a lightweight, production-ready ecommerce backend. Storefront, product catalog, cart, and orders work out of the box. Minimal complexity, maximum flexibility — install, adjust to your needs, and start selling. <p align="center">
<img src="engine/core/static/logo.png" alt="Schon Logo" width="200"/>
</p>
- Public issues: https://plane.wiseless.xyz/spaces/issues/dd33cb0ab9b04ef08a10f7eefae6d90c/?board=kanban **Schon** is a production-ready e-commerce backend. Storefront, product catalog, cart, orders, and payments work out of the box. Minimal complexity, maximum flexibility.
## Table of Contents ---
- Features ## What is Schon?
- Quick Start
- Prerequisites Schon is a complete backend solution for online stores. Whether you're launching a small shop or scaling a marketplace, Schon provides the foundation:
- Installation
- Configuration - **Ready to use** - Clone, configure, deploy. No assembly required.
- Dockerfile - **API-first** - REST and GraphQL endpoints for any frontend framework.
- nginx - **Multilingual** - 28 languages supported out of the box.
- .env - **Extensible** - Modular Django apps, easy to customize.
- Usage
- Contributing ---
- Contact
- License
## Features ## Features
- Modular backend, easy to extend and customize | Category | Details |
- Dockerized deployment with Docker Compose |----------|---------|
- Celery workers and beat for background tasks | **Framework** | Django 5.2, Django REST Framework 3.16, Graphene-Django |
- REST and GraphQL APIs | **Database** | PostgreSQL with PostGIS, Redis caching, Elasticsearch search |
- Internationalization with modeltranslation | **Tasks** | Celery workers with Redis broker, scheduled tasks with Beat |
- Redis-based caching and queues | **Auth** | JWT authentication, rate limiting, custom user model (email-based) |
- JWT auth and rate limiting | **APIs** | REST + GraphQL, Swagger/ReDoc documentation |
| **i18n** | 28 languages, model translation support |
| **Deployment** | Docker Compose or native Linux with systemd |
---
## Quick Start ## Quick Start
### Prerequisites ### Prerequisites
- Docker and Docker Compose - Git
- Docker and Docker Compose **or** Linux with Python 3.12+, PostgreSQL, Redis, Elasticsearch
### Installation ### Docker (Recommended for Development)
1. Clone the repository ```bash
```bash # Clone the repository
git clone https://gitlab.com/wiseless.xyz/eVibes.git git clone https://git.wiseless.xyz/fureunoir/schon
cd eVibes cd schon
```
2. Choose a storefront (optional). The `main` branch ships without a storefront. If you want one, pick a branch: # Generate environment file
```bash make generate-env
git checkout storefront-<nuxt|next|sk|qwik>
```
3. Generate your .env file and review its values # Review and adjust .env as needed
- Windows nano .env
```powershell
scripts\Windows\generate-environment-file.ps1
```
- Unix
```bash
scripts/Unix/generate-environment-file.sh
```
4. Install dependencies # Install (pull and build images)
- Windows make install
```powershell
scripts\Windows\install.ps1
```
- Unix
```bash
scripts/Unix/install.sh
```
5. Run the stack # Start all services
- Windows make run
```powershell ```
scripts\Windows\run.ps1
```
- Unix
```bash
scripts/Unix/run.sh
```
6. Production checklist ### Native Linux (Production)
- Include `nginx.conf` into your Nginx setup
- Issue TLS certs with Certbot (https://certbot.eff.org/) ```bash
# Clone to /opt/schon
sudo git clone https://git.wiseless.xyz/fureunoir/schon /opt/schon
cd /opt/schon
# Generate environment file
make generate-env
# Review and adjust .env
sudo nano .env
# Install (creates schon user, syncs dependencies, configures systemd)
sudo make install
# Select option 2: Native Linux
# Start services
sudo systemctl start schon-web schon-worker schon-beat schon-stock-updater
```
### Storefronts
The `main` branch ships backend-only. For a complete store with frontend:
```bash
git checkout storefront-<nuxt|next|sk|qwik>
```
---
## Configuration ## Configuration
### Dockerfile ### Environment Variables
If you rely on locale mirrors, adjust Debian sources before running installation scripts:
``` After running `make generate-env`, review `.env`:
RUN sed -i 's|https://deb.debian.org/debian|https://ftp.<locale>.debian.org/debian|g' /etc/apt/sources.list.d/debian.sources
| Variable | Description |
|----------|-------------|
| `DEBUG` | Set to `0` for production |
| `SECRET_KEY` | Django secret key (auto-generated) |
| `JWT_SIGNING_KEY` | JWT token signing key (auto-generated) |
| `POSTGRES_*` | Database credentials |
| `REDIS_PASSWORD` | Redis authentication |
| `SCHON_PROJECT_NAME` | Your store name |
| `SCHON_BASE_DOMAIN` | Your domain (e.g., `example.com`) |
### Nginx
1. Copy the example config:
```bash
sudo cp nginx.example.conf /etc/nginx/sites-available/schon
```
2. Update domain names and paths in the config
3. Enable the site:
```bash
sudo ln -s /etc/nginx/sites-available/schon /etc/nginx/sites-enabled/
```
4. Obtain SSL certificates:
```bash
sudo certbot --nginx -d api.yourdomain.com -d yourdomain.com -d www.yourdomain.com
```
5. Reload Nginx:
```bash
sudo systemctl reload nginx
```
### DNS Records
Configure these DNS records pointing to your server:
- `yourdomain.com` (A record)
- `www.yourdomain.com` (A or CNAME)
- `api.yourdomain.com` (A or CNAME)
- `prometheus.yourdomain.com` (A or CNAME, optional)
---
## API Documentation
Once running, access the API documentation:
| Endpoint | Description |
|----------|-------------|
| `http://api.localhost:8000/` | API root / Admin redirect |
| `http://api.localhost:8000/docs/swagger/` | Swagger UI |
| `http://api.localhost:8000/docs/redoc/` | ReDoc |
| `http://api.localhost:8000/graphql/` | GraphQL Playground |
| `http://api.localhost:8000/admin/` | Django Admin |
| `http://api.localhost:8000/health/` | Health check endpoint |
Authentication header: `X-SCHON-AUTH: Bearer <token>`
---
## Development
### Commands
```bash
make run # Start services
make restart # Restart services
make test # Run tests with coverage
make format # Format code with Ruff
make check # Lint code with Ruff
make typecheck # Type check with ty
make precommit # Run format, check, typecheck
make make-messages # Extract translation strings
make compile-messages # Compile translations
make backup # Create database backup
``` ```
### nginx ### Running Migrations
- Comment out SSL-related lines
- Apply your domain-specific settings
- Run `certbot --cert-only --nginx`
- Uncomment SSL lines and reload Nginx
### .env ```bash
After generation, review and update secrets and credentials (API keys, DB password, Redis password, etc.). # Docker
docker compose exec app uv run python manage.py migrate
## Usage # Native
cd /opt/schon && .venv/bin/python manage.py migrate
```
- DNS records youll typically want: ### Creating a Superuser
1. @.your-domain.com
2. www.your-domain.com
3. api.your-domain.com
4. prometheus.your-domain.com
- Once running, access: ```bash
- API root / Admin redirect: http://api.localhost:8000/ # Docker
- REST docs: http://api.localhost:8000/docs/swagger or http://api.localhost:8000/docs/redoc docker compose exec app uv run python manage.py createsuperuser
- GraphQL: http://api.localhost:8000/graphql/
## Contributing # Native
cd /opt/schon && .venv/bin/python manage.py createsuperuser
```
- Track and report issues here: https://plane.wiseless.xyz/spaces/issues/dd33cb0ab9b04ef08a10f7eefae6d90c/?board=list ---
- Pull requests are welcome. Please keep changes minimal and focused.
## Contact ## Project Structure
- Author: Egor "fureunoir" Gorbunov ```
- Email: contact@fureunoir.com schon/
- Telegram: https://t.me/fureunoir ├── schon/ # Django project settings
│ ├── settings/ # Split settings (base, drf, celery, etc.)
│ ├── middleware.py # Custom middleware
│ └── urls.py # URL routing
├── engine/ # Django apps
│ ├── core/ # Products, orders, categories, vendors
│ ├── payments/ # Transactions, payment gateways
│ ├── vibes_auth/ # Custom User model, JWT auth
│ └── blog/ # Blog posts
├── Dockerfiles/ # Docker configurations
├── systemd/ # Systemd service files
├── scripts/ # Installation and utility scripts
└── nginx.example.conf # Nginx configuration template
```
---
## Feedback & Issues
We value your feedback. Please open issues for:
- Bug reports
- Feature suggestions
- Questions about usage
**Issue Tracker:** [Plane](https://plane.wiseless.xyz/spaces/issues/dd33cb0ab9b04ef08a10f7eefae6d90c/?board=kanban)
Due to licensing restrictions, we cannot accept pull requests. See the LICENSE file for details.
---
## License ## License
This project is licensed under the terms of the LICENSE file included in this repository. Schon is released under a custom license. Key points:
![FAVICON](engine/core/docs/images/evibes.png) - **Non-commercial use**: Free for personal, academic, and non-commercial purposes
- **Commercial use**: Requires written authorization or automatic 8% royalty
See the [LICENSE](LICENSE) file for complete terms.
---
## Contact
**Author:** Egor "fureunoir" Gorbunov
- Email: [contact@fureunoir.com](mailto:contact@fureunoir.com)
- Telegram: [@fureunoir](https://t.me/fureunoir)
---
<p align="center">
<sub>Built with care by the Wiseless Team</sub>
</p>

View file

@ -0,0 +1,7 @@
services:
database:
ports:
- "5432:5432"
elasticsearch:
ports:
- "9200:9200"

View file

@ -32,8 +32,6 @@ services:
restart: always restart: always
volumes: volumes:
- postgres-data:/var/lib/postgresql/data/ - postgres-data:/var/lib/postgresql/data/
ports:
- "5432:5432"
env_file: env_file:
- .env - .env
logging: *default-logging logging: *default-logging
@ -90,21 +88,22 @@ services:
elasticsearch: elasticsearch:
container_name: elasticsearch container_name: elasticsearch
image: wiseless/elasticsearch-maxed:8.16.6 image: git.wiseless.xyz/fureunoir/schon-elasticsearch:9.3.1
restart: always restart: always
environment: environment:
- discovery.type=single-node - discovery.type=single-node
- ES_JAVA_OPTS=-Xms512m -Xmx512m - ES_JAVA_OPTS=-Xms512m -Xmx512m
- xpack.security.enabled=false - xpack.security.enabled=true
- xpack.security.http.ssl.enabled=false
- xpack.security.transport.ssl.enabled=false
- ELASTIC_PASSWORD=${ELASTIC_PASSWORD}
env_file: env_file:
- .env - .env
ports:
- "9200:9200"
volumes: volumes:
- es-data:/usr/share/elasticsearch/data - es-data:/usr/share/elasticsearch/data
logging: *default-logging logging: *default-logging
healthcheck: healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:9200" ] test: [ "CMD", "curl", "-f", "-u", "elastic:${ELASTIC_PASSWORD}", "http://localhost:9200/_cluster/health" ]
interval: 10s interval: 10s
timeout: 5s timeout: 5s
retries: 5 retries: 5
@ -118,8 +117,6 @@ services:
- .env - .env
command: command:
- "--es.uri=http://elastic:${ELASTIC_PASSWORD}@elasticsearch:9200" - "--es.uri=http://elastic:${ELASTIC_PASSWORD}@elasticsearch:9200"
ports:
- "9114:9114"
depends_on: depends_on:
elasticsearch: elasticsearch:
condition: service_healthy condition: service_healthy
@ -147,7 +144,7 @@ services:
condition: service_healthy condition: service_healthy
logging: *default-logging logging: *default-logging
healthcheck: healthcheck:
test: [ "CMD-SHELL", "celery -A evibes status | grep -q 'OK'" ] test: [ "CMD-SHELL", "celery -A schon status | grep -q 'OK'" ]
interval: 30s interval: 30s
timeout: 10s timeout: 10s
retries: 5 retries: 5
@ -175,7 +172,7 @@ services:
condition: service_healthy condition: service_healthy
logging: *default-logging logging: *default-logging
healthcheck: healthcheck:
test: [ "CMD-SHELL", "celery -A evibes status | grep -q 'OK'" ] test: [ "CMD-SHELL", "celery -A schon status | grep -q 'OK'" ]
interval: 30s interval: 30s
timeout: 10s timeout: 10s
retries: 5 retries: 5
@ -210,12 +207,12 @@ services:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ./monitoring/web.yml:/etc/prometheus/web.yml:ro - ./monitoring/web.yml:/etc/prometheus/web.yml:ro
- prometheus-data:/prometheus - prometheus-data:/prometheus
ports:
- "9090:9090"
command: command:
- --config.file=/etc/prometheus/prometheus.yml - --config.file=/etc/prometheus/prometheus.yml
- --web.config.file=/etc/prometheus/web.yml - --web.config.file=/etc/prometheus/web.yml
logging: *default-logging logging: *default-logging
ports:
- "9090:9090"
volumes: volumes:

View file

@ -1,6 +1,7 @@
from django.contrib.admin import register from django.contrib.admin import register
from django.db.models import TextField from django.db.models import TextField
from django_summernote.admin import SummernoteModelAdminMixin from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _
from unfold.admin import ModelAdmin from unfold.admin import ModelAdmin
from unfold_markdown import MarkdownWidget from unfold_markdown import MarkdownWidget
@ -9,9 +10,7 @@ from engine.core.admin import ActivationActionsMixin, FieldsetsMixin
@register(Post) @register(Post)
class PostAdmin( class PostAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
SummernoteModelAdminMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin
): # type: ignore [misc, type-arg]
list_display = ("title", "author", "slug", "created", "modified") list_display = ("title", "author", "slug", "created", "modified")
list_filter = ("author", "tags", "created", "modified") list_filter = ("author", "tags", "created", "modified")
search_fields = ("title", "content", "slug") search_fields = ("title", "content", "slug")
@ -24,23 +23,34 @@ class PostAdmin(
"slug", "slug",
"modified", "modified",
"created", "created",
"blogpic_preview",
) )
summernote_fields = ("content",)
general_fields = [ general_fields = [
"title", "title",
"author",
"content", "content",
"file", "file",
"blogpic",
"blogpic_preview",
] ]
relation_fields = [ relation_fields = [
"author", "author",
"tags", "tags",
] ]
def blogpic_preview(self, obj: Post):
if obj.blogpic:
return format_html(
'<img src="{}" style="max-width:400px;max-height:300px;object-fit:contain">',
obj.blogpic.url,
)
return ""
blogpic_preview.short_description = _("picture preview") # ty:ignore[unresolved-attribute]
@register(PostTag) @register(PostTag)
class PostTagAdmin(ModelAdmin): # type: ignore [type-arg] class PostTagAdmin(ModelAdmin):
list_display = ("tag_name", "name") list_display = ("tag_name", "name")
search_fields = ("tag_name", "name") search_fields = ("tag_name", "name")
ordering = ("tag_name",) ordering = ("tag_name",)

View file

@ -9,5 +9,5 @@ class BlogConfig(AppConfig):
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
def ready(self) -> None: def ready(self) -> None:
import engine.blog.elasticsearch.documents import engine.blog.elasticsearch.documents # noqa: F401
import engine.blog.signals # noqa: F401 import engine.blog.signals # noqa: F401

View file

@ -10,7 +10,7 @@ from engine.core.elasticsearch import (
from engine.core.elasticsearch.documents import BaseDocument from engine.core.elasticsearch.documents import BaseDocument
class PostDocument(ActiveOnlyMixin, BaseDocument): # type: ignore [misc] class PostDocument(ActiveOnlyMixin, BaseDocument):
title = fields.TextField( title = fields.TextField(
attr="title", attr="title",
analyzer="standard", analyzer="standard",

View file

@ -4,7 +4,7 @@ from engine.blog.models import Post
from engine.core.filters import CaseInsensitiveListFilter from engine.core.filters import CaseInsensitiveListFilter
class PostFilter(FilterSet): # type: ignore [misc] class PostFilter(FilterSet):
uuid = UUIDFilter(field_name="uuid", lookup_expr="exact") uuid = UUIDFilter(field_name="uuid", lookup_expr="exact")
slug = CharFilter(field_name="slug", lookup_expr="exact") slug = CharFilter(field_name="slug", lookup_expr="exact")
author = UUIDFilter(field_name="author__uuid", lookup_expr="exact") author = UUIDFilter(field_name="author__uuid", lookup_expr="exact")

View file

@ -1,21 +1,26 @@
import graphene from django.http import HttpRequest
from graphene import relay from graphene import List, String, relay
from graphene_django import DjangoObjectType from graphene_django import DjangoObjectType
from engine.blog.models import Post, PostTag from engine.blog.models import Post, PostTag
from engine.core.utils.markdown import render_markdown
class PostType(DjangoObjectType): class PostType(DjangoObjectType):
tags = graphene.List(lambda: PostTagType) tags = List(lambda: PostTagType)
content = graphene.String() content = String()
blogpic = String()
class Meta: class Meta:
model = Post model = Post
fields = ["tags", "content", "title", "slug"] fields = ["tags", "content", "title", "slug", "blogpic"]
interfaces = (relay.Node,) interfaces = (relay.Node,)
def resolve_content(self: Post, _info): def resolve_content(self: Post, _info: HttpRequest) -> str:
return self.content.html.replace("\n", "<br/>") return render_markdown(self.content or "")
def resolve_blogpic(self: Post, info: HttpRequest) -> str:
return info.build_absolute_uri(self.blogpic.url) if self.blogpic else ""
class PostTagType(DjangoObjectType): class PostTagType(DjangoObjectType):

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2025.4\n" "Project-Id-Version: SCHON 2026.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-10 21:44+0300\n" "POT-Creation-Date: 2026-03-05 16:16+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
@ -13,6 +13,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: engine/blog/admin.py:49
msgid "picture preview"
msgstr "معاينة الصورة"
#: engine/blog/apps.py:8 #: engine/blog/apps.py:8
msgid "blog" msgid "blog"
msgstr "المدونة" msgstr "المدونة"
@ -25,29 +29,38 @@ msgstr "سرد جميع المشاركات (للقراءة فقط)"
msgid "retrieve a single post (read-only)" msgid "retrieve a single post (read-only)"
msgstr "استرداد منشور واحد (للقراءة فقط)" msgstr "استرداد منشور واحد (للقراءة فقط)"
#: engine/blog/models.py:13 #: engine/blog/models.py:22
msgid "" msgid ""
"Represents a blog post model. The Post class defines the structure and " "Represents a blog post model. The Post class defines the structure and "
"behavior of a blog post. It includes attributes for author, title, content, " "behavior of a blog post. It includes attributes for author, title, content, "
"optional file attachment, slug, and associated tags. The class enforces " "optional file attachment, slug, and associated tags. The class enforces "
"constraints such as requiring either content or a file attachment but not " "constraints such as requiring either content or a file attachment but not "
"both simultaneously. It also supports automatic slug generation based on the " "both simultaneously. It also supports automatic slug generation based on the"
"title." " title."
msgstr "" msgstr ""
"يمثل نموذج منشور المدونة. تحدد فئة المشاركة بنية وسلوك مشاركة المدونة. " "يمثل نموذج منشور المدونة. تحدد فئة المشاركة بنية وسلوك مشاركة المدونة. "
"وتتضمن سمات للمؤلف والعنوان والمحتوى ومرفق الملف الاختياري والسبيكة " "وتتضمن سمات للمؤلف والعنوان والمحتوى ومرفق الملف الاختياري والسبيكة "
"والعلامات المرتبطة بها. يفرض الصنف قيودًا مثل طلب إما محتوى أو مرفق ملف ولكن " "والعلامات المرتبطة بها. يفرض الصنف قيودًا مثل طلب إما محتوى أو مرفق ملف ولكن"
"ليس كلاهما في نفس الوقت. كما أنها تدعم إنشاء سبيكة تلقائية بناءً على العنوان." " ليس كلاهما في نفس الوقت. كما أنها تدعم إنشاء سبيكة تلقائية بناءً على "
"العنوان."
#: engine/blog/models.py:24 #: engine/blog/models.py:43
msgid "post title" msgid "post title"
msgstr "عنوان المنشور" msgstr "عنوان المنشور"
#: engine/blog/models.py:24 #: engine/blog/models.py:44
msgid "title" msgid "title"
msgstr "العنوان" msgstr "العنوان"
#: engine/blog/models.py:69 #: engine/blog/models.py:47
msgid "content"
msgstr "المحتوى"
#: engine/blog/models.py:48
msgid "post content"
msgstr "نشر المحتوى"
#: engine/blog/models.py:68
msgid "is static page" msgid "is static page"
msgstr "هي صفحة ثابتة" msgstr "هي صفحة ثابتة"
@ -56,45 +69,44 @@ msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?"
msgstr "" msgstr ""
"هل هذا منشور لصفحة ذات عنوان URL ثابت (على سبيل المثال '/مساعدة/التسليم'؟)" "هل هذا منشور لصفحة ذات عنوان URL ثابت (على سبيل المثال '/مساعدة/التسليم'؟)"
#: engine/blog/models.py:77 #: engine/blog/models.py:82
msgid "post" msgid "post"
msgstr "المنشور" msgstr "المنشور"
#: engine/blog/models.py:78 #: engine/blog/models.py:83
msgid "posts" msgid "posts"
msgstr "المنشورات" msgstr "المنشورات"
#: engine/blog/models.py:82 #: engine/blog/models.py:88
msgid "markdown files are not supported yet - use markdown content instead" msgid "file uploads are not supported yet - use content instead"
msgstr "" msgstr ""
"ملفات تخفيض السعر غير مدعومة Yer - استخدم محتوى تخفيض السعر بدلاً من ذلك!" "ملفات تخفيض السعر غير مدعومة Yer - استخدم محتوى تخفيض السعر بدلاً من ذلك!"
#: engine/blog/models.py:84 #: engine/blog/models.py:92
msgid "" msgid "a file or content must be provided - mutually exclusive"
"a markdown file or markdown content must be provided - mutually exclusive" msgstr "يجب توفير ملف تخفيض أو محتوى تخفيض - متبادل الاستبعاد"
msgstr "يجب توفير ملف ترميز أو محتوى ترميز مخفض - متنافيان"
#: engine/blog/models.py:116 #: engine/blog/models.py:125
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "معرّف العلامة الداخلي لعلامة المنشور" msgstr "معرّف العلامة الداخلي لعلامة المنشور"
#: engine/blog/models.py:117 #: engine/blog/models.py:126
msgid "tag name" msgid "tag name"
msgstr "اسم العلامة" msgstr "اسم العلامة"
#: engine/blog/models.py:121 #: engine/blog/models.py:130
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "اسم سهل الاستخدام لعلامة المنشور" msgstr "اسم سهل الاستخدام لعلامة المنشور"
#: engine/blog/models.py:122 #: engine/blog/models.py:131
msgid "tag display name" msgid "tag display name"
msgstr "اسم عرض العلامة" msgstr "اسم عرض العلامة"
#: engine/blog/models.py:130 #: engine/blog/models.py:139
msgid "post tag" msgid "post tag"
msgstr "علامة المشاركة" msgstr "علامة المشاركة"
#: engine/blog/models.py:131 #: engine/blog/models.py:140
msgid "post tags" msgid "post tags"
msgstr "علامات المشاركة" msgstr "علامات المشاركة"

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2025.4\n" "Project-Id-Version: SCHON 2026.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-10 21:44+0300\n" "POT-Creation-Date: 2026-03-05 16:16+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
@ -13,6 +13,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: engine/blog/admin.py:49
msgid "picture preview"
msgstr "Náhled obrázku"
#: engine/blog/apps.py:8 #: engine/blog/apps.py:8
msgid "blog" msgid "blog"
msgstr "Blog" msgstr "Blog"
@ -25,80 +29,87 @@ msgstr "Seznam všech příspěvků (pouze pro čtení)"
msgid "retrieve a single post (read-only)" msgid "retrieve a single post (read-only)"
msgstr "Získání jednoho příspěvku (pouze pro čtení)" msgstr "Získání jednoho příspěvku (pouze pro čtení)"
#: engine/blog/models.py:13 #: engine/blog/models.py:22
msgid "" msgid ""
"Represents a blog post model. The Post class defines the structure and " "Represents a blog post model. The Post class defines the structure and "
"behavior of a blog post. It includes attributes for author, title, content, " "behavior of a blog post. It includes attributes for author, title, content, "
"optional file attachment, slug, and associated tags. The class enforces " "optional file attachment, slug, and associated tags. The class enforces "
"constraints such as requiring either content or a file attachment but not " "constraints such as requiring either content or a file attachment but not "
"both simultaneously. It also supports automatic slug generation based on the " "both simultaneously. It also supports automatic slug generation based on the"
"title." " title."
msgstr "" msgstr ""
"Představuje model příspěvku na blogu. Třída Post definuje strukturu a " "Představuje model příspěvku na blogu. Třída Post definuje strukturu a "
"chování příspěvku na blogu. Obsahuje atributy pro autora, název, obsah, " "chování příspěvku na blogu. Obsahuje atributy pro autora, název, obsah, "
"volitelnou přílohu, slug a přidružené značky. Třída vynucuje omezení, jako " "volitelnou přílohu, slug a přidružené značky. Třída vynucuje omezení, jako "
"je požadavek na obsah nebo přílohu souboru, ale ne obojí současně. Podporuje " "je požadavek na obsah nebo přílohu souboru, ale ne obojí současně. Podporuje"
"také automatické generování slugu na základě názvu." " také automatické generování slugu na základě názvu."
#: engine/blog/models.py:24 #: engine/blog/models.py:43
msgid "post title" msgid "post title"
msgstr "Název příspěvku" msgstr "Název příspěvku"
#: engine/blog/models.py:24 #: engine/blog/models.py:44
msgid "title" msgid "title"
msgstr "Název" msgstr "Název"
#: engine/blog/models.py:69 #: engine/blog/models.py:47
msgid "content"
msgstr "obsah"
#: engine/blog/models.py:48
msgid "post content"
msgstr "obsah příspěvku"
#: engine/blog/models.py:68
msgid "is static page" msgid "is static page"
msgstr "je statická stránka" msgstr "je statická stránka"
#: engine/blog/models.py:70 #: engine/blog/models.py:70
msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?" msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?"
msgstr "" msgstr ""
"je to příspěvek pro stránku se statickou adresou URL (např. `/help/" "je to příspěvek pro stránku se statickou adresou URL (např. "
"delivery`)?" "`/help/delivery`)?"
#: engine/blog/models.py:77 #: engine/blog/models.py:82
msgid "post" msgid "post"
msgstr "Příspěvek" msgstr "Příspěvek"
#: engine/blog/models.py:78 #: engine/blog/models.py:83
msgid "posts" msgid "posts"
msgstr "Příspěvky" msgstr "Příspěvky"
#: engine/blog/models.py:82 #: engine/blog/models.py:88
msgid "markdown files are not supported yet - use markdown content instead" msgid "file uploads are not supported yet - use content instead"
msgstr "" msgstr ""
"Soubory Markdown nejsou podporovány - místo toho použijte obsah Markdown!" "Soubory Markdown nejsou podporovány - místo toho použijte obsah Markdown!"
#: engine/blog/models.py:84 #: engine/blog/models.py:92
msgid "" msgid "a file or content must be provided - mutually exclusive"
"a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"musí být poskytnut soubor markdown nebo obsah markdown - vzájemně se " "musí být poskytnut soubor markdown nebo obsah markdown - vzájemně se "
"vylučují." "vylučují."
#: engine/blog/models.py:116 #: engine/blog/models.py:125
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "interní identifikátor tagu pro tag příspěvku" msgstr "interní identifikátor tagu pro tag příspěvku"
#: engine/blog/models.py:117 #: engine/blog/models.py:126
msgid "tag name" msgid "tag name"
msgstr "Název štítku" msgstr "Název štítku"
#: engine/blog/models.py:121 #: engine/blog/models.py:130
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "Uživatelsky přívětivý název pro značku příspěvku" msgstr "Uživatelsky přívětivý název pro značku příspěvku"
#: engine/blog/models.py:122 #: engine/blog/models.py:131
msgid "tag display name" msgid "tag display name"
msgstr "Zobrazení názvu štítku" msgstr "Zobrazení názvu štítku"
#: engine/blog/models.py:130 #: engine/blog/models.py:139
msgid "post tag" msgid "post tag"
msgstr "Označení příspěvku" msgstr "Označení příspěvku"
#: engine/blog/models.py:131 #: engine/blog/models.py:140
msgid "post tags" msgid "post tags"
msgstr "Štítky příspěvků" msgstr "Štítky příspěvků"
@ -111,8 +122,8 @@ msgid ""
"defined permissions. The view set also includes an additional 'retrieve' " "defined permissions. The view set also includes an additional 'retrieve' "
"permission configuration." "permission configuration."
msgstr "" msgstr ""
"Zapouzdřuje operace pro správu a načítání entit Post v sadě zobrazení modelu " "Zapouzdřuje operace pro správu a načítání entit Post v sadě zobrazení modelu"
"pouze pro čtení. Tato třída je přizpůsobena pro práci s aktivními objekty " " pouze pro čtení. Tato třída je přizpůsobena pro práci s aktivními objekty "
"Post a umožňuje filtrování na základě definovaných filtrů. Integruje se s " "Post a umožňuje filtrování na základě definovaných filtrů. Integruje se s "
"backendovým systémem filtrování Djanga a zajišťuje soulad operací s " "backendovým systémem filtrování Djanga a zajišťuje soulad operací s "
"definovanými oprávněními. Sada zobrazení obsahuje také dodatečnou " "definovanými oprávněními. Sada zobrazení obsahuje také dodatečnou "

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2025.4\n" "Project-Id-Version: SCHON 2026.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-10 21:44+0300\n" "POT-Creation-Date: 2026-03-05 16:16+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
@ -13,6 +13,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: engine/blog/admin.py:49
msgid "picture preview"
msgstr "Billedforhåndsvisning"
#: engine/blog/apps.py:8 #: engine/blog/apps.py:8
msgid "blog" msgid "blog"
msgstr "Blog" msgstr "Blog"
@ -25,31 +29,39 @@ msgstr "Vis alle indlæg (skrivebeskyttet)"
msgid "retrieve a single post (read-only)" msgid "retrieve a single post (read-only)"
msgstr "Hent et enkelt indlæg (skrivebeskyttet)" msgstr "Hent et enkelt indlæg (skrivebeskyttet)"
#: engine/blog/models.py:13 #: engine/blog/models.py:22
msgid "" msgid ""
"Represents a blog post model. The Post class defines the structure and " "Represents a blog post model. The Post class defines the structure and "
"behavior of a blog post. It includes attributes for author, title, content, " "behavior of a blog post. It includes attributes for author, title, content, "
"optional file attachment, slug, and associated tags. The class enforces " "optional file attachment, slug, and associated tags. The class enforces "
"constraints such as requiring either content or a file attachment but not " "constraints such as requiring either content or a file attachment but not "
"both simultaneously. It also supports automatic slug generation based on the " "both simultaneously. It also supports automatic slug generation based on the"
"title." " title."
msgstr "" msgstr ""
"Repræsenterer en blogindlægsmodel. Post-klassen definerer strukturen og " "Repræsenterer en blogindlægsmodel. Post-klassen definerer strukturen og "
"adfærden i et blogindlæg. Den indeholder attributter for forfatter, titel, " "adfærden i et blogindlæg. Den indeholder attributter for forfatter, titel, "
"indhold, valgfri vedhæftet fil, slug og tilknyttede tags. Klassen håndhæver " "indhold, valgfri vedhæftet fil, slug og tilknyttede tags. Klassen håndhæver "
"begrænsninger som f.eks. at kræve enten indhold eller en vedhæftet fil, men " "begrænsninger som f.eks. at kræve enten indhold eller en vedhæftet fil, men "
"ikke begge dele på samme tid. Den understøtter også automatisk generering af " "ikke begge dele på samme tid. Den understøtter også automatisk generering af"
"slugs baseret på titlen." " slugs baseret på titlen."
#: engine/blog/models.py:24 #: engine/blog/models.py:43
msgid "post title" msgid "post title"
msgstr "Indlæggets titel" msgstr "Indlæggets titel"
#: engine/blog/models.py:24 #: engine/blog/models.py:44
msgid "title" msgid "title"
msgstr "Titel" msgstr "Titel"
#: engine/blog/models.py:69 #: engine/blog/models.py:47
msgid "content"
msgstr "indhold"
#: engine/blog/models.py:48
msgid "post content"
msgstr "indhold"
#: engine/blog/models.py:68
msgid "is static page" msgid "is static page"
msgstr "er en statisk side" msgstr "er en statisk side"
@ -58,45 +70,44 @@ msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?"
msgstr "" msgstr ""
"Er dette et indlæg til en side med statisk URL (f.eks. `/help/delivery`)?" "Er dette et indlæg til en side med statisk URL (f.eks. `/help/delivery`)?"
#: engine/blog/models.py:77 #: engine/blog/models.py:82
msgid "post" msgid "post"
msgstr "Indlæg" msgstr "Indlæg"
#: engine/blog/models.py:78 #: engine/blog/models.py:83
msgid "posts" msgid "posts"
msgstr "Indlæg" msgstr "Indlæg"
#: engine/blog/models.py:82 #: engine/blog/models.py:88
msgid "markdown files are not supported yet - use markdown content instead" msgid "file uploads are not supported yet - use content instead"
msgstr "Markdown-filer understøttes ikke - brug markdown-indhold i stedet!" msgstr "filoverførsler understøttes endnu ikke brug i stedet indhold"
#: engine/blog/models.py:84 #: engine/blog/models.py:92
msgid "" msgid "a file or content must be provided - mutually exclusive"
"a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"en markdown-fil eller markdown-indhold skal leveres - gensidigt udelukkende" "en markdown-fil eller markdown-indhold skal leveres - gensidigt udelukkende"
#: engine/blog/models.py:116 #: engine/blog/models.py:125
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "intern tag-identifikator for indlægs-tagget" msgstr "intern tag-identifikator for indlægs-tagget"
#: engine/blog/models.py:117 #: engine/blog/models.py:126
msgid "tag name" msgid "tag name"
msgstr "Tag-navn" msgstr "Tag-navn"
#: engine/blog/models.py:121 #: engine/blog/models.py:130
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "Brugervenligt navn til posttagget" msgstr "Brugervenligt navn til posttagget"
#: engine/blog/models.py:122 #: engine/blog/models.py:131
msgid "tag display name" msgid "tag display name"
msgstr "Navn på tag-visning" msgstr "Navn på tag-visning"
#: engine/blog/models.py:130 #: engine/blog/models.py:139
msgid "post tag" msgid "post tag"
msgstr "Tag til indlæg" msgstr "Tag til indlæg"
#: engine/blog/models.py:131 #: engine/blog/models.py:140
msgid "post tags" msgid "post tags"
msgstr "Tags til indlæg" msgstr "Tags til indlæg"
@ -113,5 +124,6 @@ msgstr ""
"skrivebeskyttet modelvisningssæt. Denne klasse er skræddersyet til at " "skrivebeskyttet modelvisningssæt. Denne klasse er skræddersyet til at "
"håndtere Post-objekter, der er aktive, og tillader filtrering baseret på " "håndtere Post-objekter, der er aktive, og tillader filtrering baseret på "
"definerede filtre. Den integreres med Djangos backend-filtreringssystem og " "definerede filtre. Den integreres med Djangos backend-filtreringssystem og "
"sikrer, at handlingerne er i overensstemmelse med de definerede tilladelser. " "sikrer, at handlingerne er i overensstemmelse med de definerede tilladelser."
"Visningssættet indeholder også en ekstra konfiguration af tilladelsen 'hent'." " Visningssættet indeholder også en ekstra konfiguration af tilladelsen "
"'hent'."

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2025.4\n" "Project-Id-Version: SCHON 2026.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-10 21:44+0300\n" "POT-Creation-Date: 2026-03-05 16:16+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
@ -13,6 +13,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: engine/blog/admin.py:49
msgid "picture preview"
msgstr "Bildvorschau"
#: engine/blog/apps.py:8 #: engine/blog/apps.py:8
msgid "blog" msgid "blog"
msgstr "Blog" msgstr "Blog"
@ -25,14 +29,14 @@ msgstr "Alle Beiträge auflisten (schreibgeschützt)"
msgid "retrieve a single post (read-only)" msgid "retrieve a single post (read-only)"
msgstr "Einen einzelnen Beitrag abrufen (schreibgeschützt)" msgstr "Einen einzelnen Beitrag abrufen (schreibgeschützt)"
#: engine/blog/models.py:13 #: engine/blog/models.py:22
msgid "" msgid ""
"Represents a blog post model. The Post class defines the structure and " "Represents a blog post model. The Post class defines the structure and "
"behavior of a blog post. It includes attributes for author, title, content, " "behavior of a blog post. It includes attributes for author, title, content, "
"optional file attachment, slug, and associated tags. The class enforces " "optional file attachment, slug, and associated tags. The class enforces "
"constraints such as requiring either content or a file attachment but not " "constraints such as requiring either content or a file attachment but not "
"both simultaneously. It also supports automatic slug generation based on the " "both simultaneously. It also supports automatic slug generation based on the"
"title." " title."
msgstr "" msgstr ""
"Stellt ein Blogpost-Modell dar. Die Klasse Post definiert die Struktur und " "Stellt ein Blogpost-Modell dar. Die Klasse Post definiert die Struktur und "
"das Verhalten eines Blogeintrags. Sie enthält Attribute für Autor, Titel, " "das Verhalten eines Blogeintrags. Sie enthält Attribute für Autor, Titel, "
@ -42,66 +46,73 @@ msgstr ""
"unterstützt auch die automatische Slug-Generierung auf der Grundlage des " "unterstützt auch die automatische Slug-Generierung auf der Grundlage des "
"Titels." "Titels."
#: engine/blog/models.py:24 #: engine/blog/models.py:43
msgid "post title" msgid "post title"
msgstr "Titel des Beitrags" msgstr "Titel des Beitrags"
#: engine/blog/models.py:24 #: engine/blog/models.py:44
msgid "title" msgid "title"
msgstr "Titel" msgstr "Titel"
#: engine/blog/models.py:69 #: engine/blog/models.py:47
msgid "content"
msgstr "Inhalt"
#: engine/blog/models.py:48
msgid "post content"
msgstr "Beitragsinhalte"
#: engine/blog/models.py:68
msgid "is static page" msgid "is static page"
msgstr "ist eine statische Seite" msgstr "ist eine statische Seite"
#: engine/blog/models.py:70 #: engine/blog/models.py:70
msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?" msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?"
msgstr "" msgstr ""
"Ist dies ein Beitrag für eine Seite mit statischer URL (z. B. `/help/" "Ist dies ein Beitrag für eine Seite mit statischer URL (z. B. "
"delivery`)?" "`/help/delivery`)?"
#: engine/blog/models.py:77 #: engine/blog/models.py:82
msgid "post" msgid "post"
msgstr "Beitrag" msgstr "Beitrag"
#: engine/blog/models.py:78 #: engine/blog/models.py:83
msgid "posts" msgid "posts"
msgstr "Beiträge" msgstr "Beiträge"
#: engine/blog/models.py:82 #: engine/blog/models.py:88
msgid "markdown files are not supported yet - use markdown content instead" msgid "file uploads are not supported yet - use content instead"
msgstr "" msgstr ""
"Markdown-Dateien werden nicht unterstützt - verwenden Sie stattdessen " "Markdown-Dateien werden nicht unterstützt - verwenden Sie stattdessen "
"Markdown-Inhalte!" "Markdown-Inhalte!"
#: engine/blog/models.py:84 #: engine/blog/models.py:92
msgid "" msgid "a file or content must be provided - mutually exclusive"
"a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"eine Markdown-Datei oder ein Markdown-Inhalt muss bereitgestellt werden - " "eine Markdown-Datei oder ein Markdown-Inhalt muss bereitgestellt werden - "
"beide schließen sich gegenseitig aus" "beide schließen sich gegenseitig aus"
#: engine/blog/models.py:116 #: engine/blog/models.py:125
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "interner Tag-Bezeichner für den Post-Tag" msgstr "interner Tag-Bezeichner für den Post-Tag"
#: engine/blog/models.py:117 #: engine/blog/models.py:126
msgid "tag name" msgid "tag name"
msgstr "Tag name" msgstr "Tag name"
#: engine/blog/models.py:121 #: engine/blog/models.py:130
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "Benutzerfreundlicher Name für das Post-Tag" msgstr "Benutzerfreundlicher Name für das Post-Tag"
#: engine/blog/models.py:122 #: engine/blog/models.py:131
msgid "tag display name" msgid "tag display name"
msgstr "Tag-Anzeigename" msgstr "Tag-Anzeigename"
#: engine/blog/models.py:130 #: engine/blog/models.py:139
msgid "post tag" msgid "post tag"
msgstr "Tag eintragen" msgstr "Tag eintragen"
#: engine/blog/models.py:131 #: engine/blog/models.py:140
msgid "post tags" msgid "post tags"
msgstr "Tags eintragen" msgstr "Tags eintragen"
@ -119,5 +130,5 @@ msgstr ""
"aktiver Post-Objekte zugeschnitten und ermöglicht die Filterung auf der " "aktiver Post-Objekte zugeschnitten und ermöglicht die Filterung auf der "
"Grundlage definierter Filter. Sie integriert sich in das Backend-" "Grundlage definierter Filter. Sie integriert sich in das Backend-"
"Filtersystem von Django und stellt sicher, dass die Operationen mit den " "Filtersystem von Django und stellt sicher, dass die Operationen mit den "
"definierten Berechtigungen übereinstimmen. Das View Set beinhaltet auch eine " "definierten Berechtigungen übereinstimmen. Das View Set beinhaltet auch eine"
"zusätzliche 'retrieve' Berechtigungskonfiguration." " zusätzliche 'retrieve' Berechtigungskonfiguration."

View file

@ -1,13 +1,9 @@
# EVIBES GETTEXT TRANSLATIONS
# Copyright (C) 2025 EGOR <FUREUNOIR> GORBUNOV
# This file is distributed under the same license as the EVIBES package.
# EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>, 2025.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2025.4\n" "Project-Id-Version: SCHON 2026.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-10 21:44+0300\n" "POT-Creation-Date: 2026-03-05 16:16+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
@ -17,6 +13,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: engine/blog/admin.py:49
msgid "picture preview"
msgstr "Picture preview"
#: engine/blog/apps.py:8 #: engine/blog/apps.py:8
msgid "blog" msgid "blog"
msgstr "Blog" msgstr "Blog"
@ -29,31 +29,39 @@ msgstr "List all posts (read-only)"
msgid "retrieve a single post (read-only)" msgid "retrieve a single post (read-only)"
msgstr "Retrieve a single post (read-only)" msgstr "Retrieve a single post (read-only)"
#: engine/blog/models.py:13 #: engine/blog/models.py:22
msgid "" msgid ""
"Represents a blog post model. The Post class defines the structure and " "Represents a blog post model. The Post class defines the structure and "
"behavior of a blog post. It includes attributes for author, title, content, " "behavior of a blog post. It includes attributes for author, title, content, "
"optional file attachment, slug, and associated tags. The class enforces " "optional file attachment, slug, and associated tags. The class enforces "
"constraints such as requiring either content or a file attachment but not " "constraints such as requiring either content or a file attachment but not "
"both simultaneously. It also supports automatic slug generation based on the " "both simultaneously. It also supports automatic slug generation based on the"
"title." " title."
msgstr "" msgstr ""
"Represents a blog post model. The Post class defines the structure and " "Represents a blog post model. The Post class defines the structure and "
"behavior of a blog post. It includes attributes for author, title, content, " "behavior of a blog post. It includes attributes for author, title, content, "
"optional file attachment, slug, and associated tags. The class enforces " "optional file attachment, slug, and associated tags. The class enforces "
"constraints such as requiring either content or a file attachment but not " "constraints such as requiring either content or a file attachment but not "
"both simultaneously. It also supports automatic slug generation based on the " "both simultaneously. It also supports automatic slug generation based on the"
"title." " title."
#: engine/blog/models.py:24 #: engine/blog/models.py:43
msgid "post title" msgid "post title"
msgstr "Post's title" msgstr "Post's title"
#: engine/blog/models.py:24 #: engine/blog/models.py:44
msgid "title" msgid "title"
msgstr "Title" msgstr "Title"
#: engine/blog/models.py:69 #: engine/blog/models.py:47
msgid "content"
msgstr "content"
#: engine/blog/models.py:48
msgid "post content"
msgstr "post content"
#: engine/blog/models.py:68
msgid "is static page" msgid "is static page"
msgstr "is static page" msgstr "is static page"
@ -61,45 +69,44 @@ msgstr "is static page"
msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?" msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?"
msgstr "is this a post for a page with static URL (e.g. `/help/delivery`)?" msgstr "is this a post for a page with static URL (e.g. `/help/delivery`)?"
#: engine/blog/models.py:77 #: engine/blog/models.py:82
msgid "post" msgid "post"
msgstr "Post" msgstr "Post"
#: engine/blog/models.py:78 #: engine/blog/models.py:83
msgid "posts" msgid "posts"
msgstr "Posts" msgstr "Posts"
#: engine/blog/models.py:82 #: engine/blog/models.py:88
msgid "markdown files are not supported yet - use markdown content instead" msgid "file uploads are not supported yet - use content instead"
msgstr "Markdown files are not supported yer - use markdown content instead!" msgstr "file uploads are not supported yet - use content instead"
#: engine/blog/models.py:84 #: engine/blog/models.py:92
msgid "" msgid "a file or content must be provided - mutually exclusive"
"a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"a markdown file or markdown content must be provided - mutually exclusive" "a markdown file or markdown content must be provided - mutually exclusive"
#: engine/blog/models.py:116 #: engine/blog/models.py:125
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "internal tag identifier for the post tag" msgstr "internal tag identifier for the post tag"
#: engine/blog/models.py:117 #: engine/blog/models.py:126
msgid "tag name" msgid "tag name"
msgstr "Tag name" msgstr "Tag name"
#: engine/blog/models.py:121 #: engine/blog/models.py:130
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "User-friendly name for the post tag" msgstr "User-friendly name for the post tag"
#: engine/blog/models.py:122 #: engine/blog/models.py:131
msgid "tag display name" msgid "tag display name"
msgstr "Tag display name" msgstr "Tag display name"
#: engine/blog/models.py:130 #: engine/blog/models.py:139
msgid "post tag" msgid "post tag"
msgstr "Post tag" msgstr "Post tag"
#: engine/blog/models.py:131 #: engine/blog/models.py:140
msgid "post tags" msgid "post tags"
msgstr "Post tags" msgstr "Post tags"

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2025.4\n" "Project-Id-Version: SCHON 2026.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-10 21:44+0300\n" "POT-Creation-Date: 2026-03-05 16:16+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
@ -13,6 +13,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: engine/blog/admin.py:49
msgid "picture preview"
msgstr "Picture preview"
#: engine/blog/apps.py:8 #: engine/blog/apps.py:8
msgid "blog" msgid "blog"
msgstr "Blog" msgstr "Blog"
@ -25,31 +29,39 @@ msgstr "List all posts (read-only)"
msgid "retrieve a single post (read-only)" msgid "retrieve a single post (read-only)"
msgstr "Retrieve a single post (read-only)" msgstr "Retrieve a single post (read-only)"
#: engine/blog/models.py:13 #: engine/blog/models.py:22
msgid "" msgid ""
"Represents a blog post model. The Post class defines the structure and " "Represents a blog post model. The Post class defines the structure and "
"behavior of a blog post. It includes attributes for author, title, content, " "behavior of a blog post. It includes attributes for author, title, content, "
"optional file attachment, slug, and associated tags. The class enforces " "optional file attachment, slug, and associated tags. The class enforces "
"constraints such as requiring either content or a file attachment but not " "constraints such as requiring either content or a file attachment but not "
"both simultaneously. It also supports automatic slug generation based on the " "both simultaneously. It also supports automatic slug generation based on the"
"title." " title."
msgstr "" msgstr ""
"Represents a blog post model. The Post class defines the structure and " "Represents a blog post model. The Post class defines the structure and "
"behavior of a blog post. It includes attributes for author, title, content, " "behavior of a blog post. It includes attributes for author, title, content, "
"optional file attachment, slug, and associated tags. The class enforces " "optional file attachment, slug, and associated tags. The class enforces "
"constraints such as requiring either content or a file attachment but not " "constraints such as requiring either content or a file attachment but not "
"both simultaneously. It also supports automatic slug generation based on the " "both simultaneously. It also supports automatic slug generation based on the"
"title." " title."
#: engine/blog/models.py:24 #: engine/blog/models.py:43
msgid "post title" msgid "post title"
msgstr "Post's title" msgstr "Post's title"
#: engine/blog/models.py:24 #: engine/blog/models.py:44
msgid "title" msgid "title"
msgstr "Title" msgstr "Title"
#: engine/blog/models.py:69 #: engine/blog/models.py:47
msgid "content"
msgstr "content"
#: engine/blog/models.py:48
msgid "post content"
msgstr "post content"
#: engine/blog/models.py:68
msgid "is static page" msgid "is static page"
msgstr "is static page" msgstr "is static page"
@ -57,45 +69,44 @@ msgstr "is static page"
msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?" msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?"
msgstr "is this a post for a page with static URL (e.g. `/help/delivery`)?" msgstr "is this a post for a page with static URL (e.g. `/help/delivery`)?"
#: engine/blog/models.py:77 #: engine/blog/models.py:82
msgid "post" msgid "post"
msgstr "Post" msgstr "Post"
#: engine/blog/models.py:78 #: engine/blog/models.py:83
msgid "posts" msgid "posts"
msgstr "Posts" msgstr "Posts"
#: engine/blog/models.py:82 #: engine/blog/models.py:88
msgid "markdown files are not supported yet - use markdown content instead" msgid "file uploads are not supported yet - use content instead"
msgstr "Markdown files are not supported yer - use markdown content instead!" msgstr "file uploads are not supported yet - use content instead"
#: engine/blog/models.py:84 #: engine/blog/models.py:92
msgid "" msgid "a file or content must be provided - mutually exclusive"
"a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"a markdown file or markdown content must be provided - mutually exclusive" "a markdown file or markdown content must be provided - mutually exclusive"
#: engine/blog/models.py:116 #: engine/blog/models.py:125
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "internal tag identifier for the post tag" msgstr "internal tag identifier for the post tag"
#: engine/blog/models.py:117 #: engine/blog/models.py:126
msgid "tag name" msgid "tag name"
msgstr "Tag name" msgstr "Tag name"
#: engine/blog/models.py:121 #: engine/blog/models.py:130
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "User-friendly name for the post tag" msgstr "User-friendly name for the post tag"
#: engine/blog/models.py:122 #: engine/blog/models.py:131
msgid "tag display name" msgid "tag display name"
msgstr "Tag display name" msgstr "Tag display name"
#: engine/blog/models.py:130 #: engine/blog/models.py:139
msgid "post tag" msgid "post tag"
msgstr "Post tag" msgstr "Post tag"
#: engine/blog/models.py:131 #: engine/blog/models.py:140
msgid "post tags" msgid "post tags"
msgstr "Post tags" msgstr "Post tags"

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2025.4\n" "Project-Id-Version: SCHON 2026.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-10 21:44+0300\n" "POT-Creation-Date: 2026-03-05 16:16+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
@ -13,6 +13,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: engine/blog/admin.py:49
msgid "picture preview"
msgstr "Vista previa de la imagen"
#: engine/blog/apps.py:8 #: engine/blog/apps.py:8
msgid "blog" msgid "blog"
msgstr "Blog" msgstr "Blog"
@ -25,14 +29,14 @@ msgstr "Listar todos los mensajes (sólo lectura)"
msgid "retrieve a single post (read-only)" msgid "retrieve a single post (read-only)"
msgstr "Recuperar una única entrada (sólo lectura)" msgstr "Recuperar una única entrada (sólo lectura)"
#: engine/blog/models.py:13 #: engine/blog/models.py:22
msgid "" msgid ""
"Represents a blog post model. The Post class defines the structure and " "Represents a blog post model. The Post class defines the structure and "
"behavior of a blog post. It includes attributes for author, title, content, " "behavior of a blog post. It includes attributes for author, title, content, "
"optional file attachment, slug, and associated tags. The class enforces " "optional file attachment, slug, and associated tags. The class enforces "
"constraints such as requiring either content or a file attachment but not " "constraints such as requiring either content or a file attachment but not "
"both simultaneously. It also supports automatic slug generation based on the " "both simultaneously. It also supports automatic slug generation based on the"
"title." " title."
msgstr "" msgstr ""
"Representa un modelo de entrada de blog. La clase Post define la estructura " "Representa un modelo de entrada de blog. La clase Post define la estructura "
"y el comportamiento de una entrada de blog. Incluye atributos para autor, " "y el comportamiento de una entrada de blog. Incluye atributos para autor, "
@ -41,65 +45,72 @@ msgstr ""
"adjunto, pero no ambos simultáneamente. También admite la generación " "adjunto, pero no ambos simultáneamente. También admite la generación "
"automática de slug a partir del título." "automática de slug a partir del título."
#: engine/blog/models.py:24 #: engine/blog/models.py:43
msgid "post title" msgid "post title"
msgstr "Título del mensaje" msgstr "Título del mensaje"
#: engine/blog/models.py:24 #: engine/blog/models.py:44
msgid "title" msgid "title"
msgstr "Título" msgstr "Título"
#: engine/blog/models.py:69 #: engine/blog/models.py:47
msgid "content"
msgstr "contenido"
#: engine/blog/models.py:48
msgid "post content"
msgstr "publicar contenido"
#: engine/blog/models.py:68
msgid "is static page" msgid "is static page"
msgstr "es una página estática" msgstr "es una página estática"
#: engine/blog/models.py:70 #: engine/blog/models.py:70
msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?" msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?"
msgstr "" msgstr ""
"¿se trata de una entrada para una página con URL estática (por ejemplo, `/" "¿se trata de una entrada para una página con URL estática (por ejemplo, "
"help/delivery`)?" "`/help/delivery`)?"
#: engine/blog/models.py:77 #: engine/blog/models.py:82
msgid "post" msgid "post"
msgstr "Publicar en" msgstr "Publicar en"
#: engine/blog/models.py:78 #: engine/blog/models.py:83
msgid "posts" msgid "posts"
msgstr "Puestos" msgstr "Puestos"
#: engine/blog/models.py:82 #: engine/blog/models.py:88
msgid "markdown files are not supported yet - use markdown content instead" msgid "file uploads are not supported yet - use content instead"
msgstr "" msgstr ""
"No se admiten archivos Markdown - ¡utiliza contenido Markdown en su lugar!" "No se admiten archivos Markdown - ¡utiliza contenido Markdown en su lugar!"
#: engine/blog/models.py:84 #: engine/blog/models.py:92
msgid "" msgid "a file or content must be provided - mutually exclusive"
"a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"se debe proporcionar un archivo markdown o contenido markdown - mutuamente " "se debe proporcionar un archivo markdown o contenido markdown - mutuamente "
"excluyentes" "excluyentes"
#: engine/blog/models.py:116 #: engine/blog/models.py:125
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "identificador interno de la etiqueta post" msgstr "identificador interno de la etiqueta post"
#: engine/blog/models.py:117 #: engine/blog/models.py:126
msgid "tag name" msgid "tag name"
msgstr "Nombre de la etiqueta" msgstr "Nombre de la etiqueta"
#: engine/blog/models.py:121 #: engine/blog/models.py:130
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "Nombre fácil de usar para la etiqueta de la entrada" msgstr "Nombre fácil de usar para la etiqueta de la entrada"
#: engine/blog/models.py:122 #: engine/blog/models.py:131
msgid "tag display name" msgid "tag display name"
msgstr "Nombre de la etiqueta" msgstr "Nombre de la etiqueta"
#: engine/blog/models.py:130 #: engine/blog/models.py:139
msgid "post tag" msgid "post tag"
msgstr "Etiqueta postal" msgstr "Etiqueta postal"
#: engine/blog/models.py:131 #: engine/blog/models.py:140
msgid "post tags" msgid "post tags"
msgstr "Etiquetas" msgstr "Etiquetas"
@ -115,7 +126,7 @@ msgstr ""
"Encapsula operaciones para gestionar y recuperar entidades Post en un " "Encapsula operaciones para gestionar y recuperar entidades Post en un "
"conjunto de vistas de modelo de sólo lectura. Esta clase está adaptada para " "conjunto de vistas de modelo de sólo lectura. Esta clase está adaptada para "
"manejar objetos Post que están activos y permite el filtrado basado en " "manejar objetos Post que están activos y permite el filtrado basado en "
"filtros definidos. Se integra con el sistema de filtrado backend de Django y " "filtros definidos. Se integra con el sistema de filtrado backend de Django y"
"asegura que las operaciones se alinean con los permisos definidos. El " " asegura que las operaciones se alinean con los permisos definidos. El "
"conjunto de vistas también incluye una configuración adicional de permisos " "conjunto de vistas también incluye una configuración adicional de permisos "
"de \"recuperación\"." "de \"recuperación\"."

View file

@ -5,9 +5,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: SCHON 2026.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-10 21:44+0300\n" "POT-Creation-Date: 2026-03-05 16:16+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -16,6 +16,10 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: engine/blog/admin.py:49
msgid "picture preview"
msgstr ""
#: engine/blog/apps.py:8 #: engine/blog/apps.py:8
msgid "blog" msgid "blog"
msgstr "" msgstr ""
@ -28,7 +32,7 @@ msgstr ""
msgid "retrieve a single post (read-only)" msgid "retrieve a single post (read-only)"
msgstr "" msgstr ""
#: engine/blog/models.py:13 #: engine/blog/models.py:22
msgid "" msgid ""
"Represents a blog post model. The Post class defines the structure and " "Represents a blog post model. The Post class defines the structure and "
"behavior of a blog post. It includes attributes for author, title, content, " "behavior of a blog post. It includes attributes for author, title, content, "
@ -38,15 +42,23 @@ msgid ""
"title." "title."
msgstr "" msgstr ""
#: engine/blog/models.py:24 #: engine/blog/models.py:43
msgid "post title" msgid "post title"
msgstr "" msgstr ""
#: engine/blog/models.py:24 #: engine/blog/models.py:44
msgid "title" msgid "title"
msgstr "" msgstr ""
#: engine/blog/models.py:69 #: engine/blog/models.py:47
msgid "content"
msgstr ""
#: engine/blog/models.py:48
msgid "post content"
msgstr ""
#: engine/blog/models.py:68
msgid "is static page" msgid "is static page"
msgstr "" msgstr ""
@ -54,44 +66,43 @@ msgstr ""
msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?" msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?"
msgstr "" msgstr ""
#: engine/blog/models.py:77 #: engine/blog/models.py:82
msgid "post" msgid "post"
msgstr "" msgstr ""
#: engine/blog/models.py:78 #: engine/blog/models.py:83
msgid "posts" msgid "posts"
msgstr "" msgstr ""
#: engine/blog/models.py:82 #: engine/blog/models.py:88
msgid "markdown files are not supported yet - use markdown content instead" msgid "file uploads are not supported yet - use content instead"
msgstr "" msgstr ""
#: engine/blog/models.py:84 #: engine/blog/models.py:92
msgid "" msgid "a file or content must be provided - mutually exclusive"
"a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
#: engine/blog/models.py:116 #: engine/blog/models.py:125
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "" msgstr ""
#: engine/blog/models.py:117 #: engine/blog/models.py:126
msgid "tag name" msgid "tag name"
msgstr "" msgstr ""
#: engine/blog/models.py:121 #: engine/blog/models.py:130
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "" msgstr ""
#: engine/blog/models.py:122 #: engine/blog/models.py:131
msgid "tag display name" msgid "tag display name"
msgstr "" msgstr ""
#: engine/blog/models.py:130 #: engine/blog/models.py:139
msgid "post tag" msgid "post tag"
msgstr "" msgstr ""
#: engine/blog/models.py:131 #: engine/blog/models.py:140
msgid "post tags" msgid "post tags"
msgstr "" msgstr ""

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2025.4\n" "Project-Id-Version: SCHON 2026.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-10 21:44+0300\n" "POT-Creation-Date: 2026-03-05 16:16+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
@ -13,6 +13,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: engine/blog/admin.py:49
msgid "picture preview"
msgstr "Aperçu de l'image"
#: engine/blog/apps.py:8 #: engine/blog/apps.py:8
msgid "blog" msgid "blog"
msgstr "Blog" msgstr "Blog"
@ -25,14 +29,14 @@ msgstr "Liste de tous les messages (en lecture seule)"
msgid "retrieve a single post (read-only)" msgid "retrieve a single post (read-only)"
msgstr "Récupérer un seul message (en lecture seule)" msgstr "Récupérer un seul message (en lecture seule)"
#: engine/blog/models.py:13 #: engine/blog/models.py:22
msgid "" msgid ""
"Represents a blog post model. The Post class defines the structure and " "Represents a blog post model. The Post class defines the structure and "
"behavior of a blog post. It includes attributes for author, title, content, " "behavior of a blog post. It includes attributes for author, title, content, "
"optional file attachment, slug, and associated tags. The class enforces " "optional file attachment, slug, and associated tags. The class enforces "
"constraints such as requiring either content or a file attachment but not " "constraints such as requiring either content or a file attachment but not "
"both simultaneously. It also supports automatic slug generation based on the " "both simultaneously. It also supports automatic slug generation based on the"
"title." " title."
msgstr "" msgstr ""
"Représente un modèle de billet de blog. La classe Post définit la structure " "Représente un modèle de billet de blog. La classe Post définit la structure "
"et le comportement d'un billet de blog. Elle comprend des attributs pour " "et le comportement d'un billet de blog. Elle comprend des attributs pour "
@ -42,66 +46,73 @@ msgstr ""
"simultanément. Elle prend également en charge la génération automatique " "simultanément. Elle prend également en charge la génération automatique "
"d'une balise en fonction du titre." "d'une balise en fonction du titre."
#: engine/blog/models.py:24 #: engine/blog/models.py:43
msgid "post title" msgid "post title"
msgstr "Titre du message" msgstr "Titre du message"
#: engine/blog/models.py:24 #: engine/blog/models.py:44
msgid "title" msgid "title"
msgstr "Titre" msgstr "Titre"
#: engine/blog/models.py:69 #: engine/blog/models.py:47
msgid "content"
msgstr "contenu"
#: engine/blog/models.py:48
msgid "post content"
msgstr "contenu de publication"
#: engine/blog/models.py:68
msgid "is static page" msgid "is static page"
msgstr "est une page statique" msgstr "est une page statique"
#: engine/blog/models.py:70 #: engine/blog/models.py:70
msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?" msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?"
msgstr "" msgstr ""
"s'agit-il d'un message pour une page dont l'URL est statique (par exemple `/" "s'agit-il d'un message pour une page dont l'URL est statique (par exemple "
"help/delivery`) ?" "`/help/delivery`) ?"
#: engine/blog/models.py:77 #: engine/blog/models.py:82
msgid "post" msgid "post"
msgstr "Poste" msgstr "Poste"
#: engine/blog/models.py:78 #: engine/blog/models.py:83
msgid "posts" msgid "posts"
msgstr "Postes" msgstr "Postes"
#: engine/blog/models.py:82 #: engine/blog/models.py:88
msgid "markdown files are not supported yet - use markdown content instead" msgid "file uploads are not supported yet - use content instead"
msgstr "" msgstr ""
"Les fichiers Markdown ne sont pas pris en charge - utilisez plutôt du " "Les fichiers Markdown ne sont pas pris en charge - utilisez plutôt du "
"contenu Markdown !" "contenu Markdown !"
#: engine/blog/models.py:84 #: engine/blog/models.py:92
msgid "" msgid "a file or content must be provided - mutually exclusive"
"a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"un fichier markdown ou un contenu markdown doit être fourni - ils s'excluent " "un fichier markdown ou un contenu markdown doit être fourni - ils s'excluent"
"mutuellement" " mutuellement"
#: engine/blog/models.py:116 #: engine/blog/models.py:125
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "identifiant interne de la balise post" msgstr "identifiant interne de la balise post"
#: engine/blog/models.py:117 #: engine/blog/models.py:126
msgid "tag name" msgid "tag name"
msgstr "Nom du jour" msgstr "Nom du jour"
#: engine/blog/models.py:121 #: engine/blog/models.py:130
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "Nom convivial pour la balise post" msgstr "Nom convivial pour la balise post"
#: engine/blog/models.py:122 #: engine/blog/models.py:131
msgid "tag display name" msgid "tag display name"
msgstr "Nom d'affichage de l'étiquette" msgstr "Nom d'affichage de l'étiquette"
#: engine/blog/models.py:130 #: engine/blog/models.py:139
msgid "post tag" msgid "post tag"
msgstr "Tag de poste" msgstr "Tag de poste"
#: engine/blog/models.py:131 #: engine/blog/models.py:140
msgid "post tags" msgid "post tags"
msgstr "Tags de la poste" msgstr "Tags de la poste"
@ -114,8 +125,8 @@ msgid ""
"defined permissions. The view set also includes an additional 'retrieve' " "defined permissions. The view set also includes an additional 'retrieve' "
"permission configuration." "permission configuration."
msgstr "" msgstr ""
"Encapsule les opérations de gestion et d'extraction des entités Post dans un " "Encapsule les opérations de gestion et d'extraction des entités Post dans un"
"ensemble de vues de modèle en lecture seule. Cette classe est conçue pour " " ensemble de vues de modèle en lecture seule. Cette classe est conçue pour "
"gérer les objets Post qui sont actifs et permet un filtrage basé sur des " "gérer les objets Post qui sont actifs et permet un filtrage basé sur des "
"filtres définis. Elle s'intègre au système de filtrage du backend de Django " "filtres définis. Elle s'intègre au système de filtrage du backend de Django "
"et garantit que les opérations s'alignent sur les permissions définies. Le " "et garantit que les opérations s'alignent sur les permissions définies. Le "

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2025.4\n" "Project-Id-Version: SCHON 2026.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-10 21:44+0300\n" "POT-Creation-Date: 2026-03-05 16:16+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
@ -13,6 +13,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: engine/blog/admin.py:49
msgid "picture preview"
msgstr "תצוגה מקדימה של תמונה"
#: engine/blog/apps.py:8 #: engine/blog/apps.py:8
msgid "blog" msgid "blog"
msgstr "בלוג" msgstr "בלוג"
@ -25,29 +29,37 @@ msgstr "הצג את כל ההודעות (לקריאה בלבד)"
msgid "retrieve a single post (read-only)" msgid "retrieve a single post (read-only)"
msgstr "איתור פוסט בודד (לקריאה בלבד)" msgstr "איתור פוסט בודד (לקריאה בלבד)"
#: engine/blog/models.py:13 #: engine/blog/models.py:22
msgid "" msgid ""
"Represents a blog post model. The Post class defines the structure and " "Represents a blog post model. The Post class defines the structure and "
"behavior of a blog post. It includes attributes for author, title, content, " "behavior of a blog post. It includes attributes for author, title, content, "
"optional file attachment, slug, and associated tags. The class enforces " "optional file attachment, slug, and associated tags. The class enforces "
"constraints such as requiring either content or a file attachment but not " "constraints such as requiring either content or a file attachment but not "
"both simultaneously. It also supports automatic slug generation based on the " "both simultaneously. It also supports automatic slug generation based on the"
"title." " title."
msgstr "" msgstr ""
"מייצג מודל של פוסט בבלוג. מחלקת Post מגדירה את המבנה וההתנהגות של פוסט " "מייצג מודל של פוסט בבלוג. מחלקת Post מגדירה את המבנה וההתנהגות של פוסט "
"בבלוג. היא כוללת תכונות עבור מחבר, כותרת, תוכן, קובץ מצורף אופציונלי, slug " "בבלוג. היא כוללת תכונות עבור מחבר, כותרת, תוכן, קובץ מצורף אופציונלי, slug "
"ותגיות נלוות. המחלקה אוכפת אילוצים כגון דרישה לתוכן או לקובץ מצורף, אך לא " "ותגיות נלוות. המחלקה אוכפת אילוצים כגון דרישה לתוכן או לקובץ מצורף, אך לא "
"לשניהם בו-זמנית. היא תומכת גם ביצירה אוטומטית של slug על סמך הכותרת." "לשניהם בו-זמנית. היא תומכת גם ביצירה אוטומטית של slug על סמך הכותרת."
#: engine/blog/models.py:24 #: engine/blog/models.py:43
msgid "post title" msgid "post title"
msgstr "כותרת הפוסט" msgstr "כותרת הפוסט"
#: engine/blog/models.py:24 #: engine/blog/models.py:44
msgid "title" msgid "title"
msgstr "כותרת" msgstr "כותרת"
#: engine/blog/models.py:69 #: engine/blog/models.py:47
msgid "content"
msgstr "תוכן"
#: engine/blog/models.py:48
msgid "post content"
msgstr "תוכן הפוסט"
#: engine/blog/models.py:68
msgid "is static page" msgid "is static page"
msgstr "הוא דף סטטי" msgstr "הוא דף סטטי"
@ -55,44 +67,43 @@ msgstr "הוא דף סטטי"
msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?" msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?"
msgstr "האם זו הודעה לדף עם כתובת URL סטטית (למשל `/help/delivery`)?" msgstr "האם זו הודעה לדף עם כתובת URL סטטית (למשל `/help/delivery`)?"
#: engine/blog/models.py:77 #: engine/blog/models.py:82
msgid "post" msgid "post"
msgstr "פוסט" msgstr "פוסט"
#: engine/blog/models.py:78 #: engine/blog/models.py:83
msgid "posts" msgid "posts"
msgstr "פוסטים" msgstr "פוסטים"
#: engine/blog/models.py:82 #: engine/blog/models.py:88
msgid "markdown files are not supported yet - use markdown content instead" msgid "file uploads are not supported yet - use content instead"
msgstr "קובצי Markdown אינם נתמכים עדיין - השתמש בתוכן Markdown במקום!" msgstr "העלאת קבצים אינה נתמכת עדיין - השתמש בתוכן במקום זאת"
#: engine/blog/models.py:84 #: engine/blog/models.py:92
msgid "" msgid "a file or content must be provided - mutually exclusive"
"a markdown file or markdown content must be provided - mutually exclusive"
msgstr "יש לספק קובץ markdown או תוכן markdown - באופן בלעדי" msgstr "יש לספק קובץ markdown או תוכן markdown - באופן בלעדי"
#: engine/blog/models.py:116 #: engine/blog/models.py:125
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "מזהה תגיות פנימי עבור תגיות הפוסט" msgstr "מזהה תגיות פנימי עבור תגיות הפוסט"
#: engine/blog/models.py:117 #: engine/blog/models.py:126
msgid "tag name" msgid "tag name"
msgstr "שם היום" msgstr "שם היום"
#: engine/blog/models.py:121 #: engine/blog/models.py:130
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "שם ידידותי למשתמש עבור תגיות הפוסט" msgstr "שם ידידותי למשתמש עבור תגיות הפוסט"
#: engine/blog/models.py:122 #: engine/blog/models.py:131
msgid "tag display name" msgid "tag display name"
msgstr "שם תצוגה של התג" msgstr "שם תצוגה של התג"
#: engine/blog/models.py:130 #: engine/blog/models.py:139
msgid "post tag" msgid "post tag"
msgstr "תגית פוסט" msgstr "תגית פוסט"
#: engine/blog/models.py:131 #: engine/blog/models.py:140
msgid "post tags" msgid "post tags"
msgstr "תגיות פוסט" msgstr "תגיות פוסט"
@ -105,7 +116,7 @@ msgid ""
"defined permissions. The view set also includes an additional 'retrieve' " "defined permissions. The view set also includes an additional 'retrieve' "
"permission configuration." "permission configuration."
msgstr "" msgstr ""
"מכיל פעולות לניהול ואחזור ישויות Post במערך תצוגה של מודל לקריאה בלבד. מחלקה " "מכיל פעולות לניהול ואחזור ישויות Post במערך תצוגה של מודל לקריאה בלבד. מחלקה"
"זו מותאמת לטיפול באובייקטי Post פעילים ומאפשרת סינון על בסיס מסננים מוגדרים. " " זו מותאמת לטיפול באובייקטי Post פעילים ומאפשרת סינון על בסיס מסננים "
"היא משתלבת במערכת הסינון האחורית של Django ומבטיחה שהפעולות תואמות את " "מוגדרים. היא משתלבת במערכת הסינון האחורית של Django ומבטיחה שהפעולות תואמות "
"ההרשאות המוגדרות. מערך התצוגה כולל גם תצורת הרשאה נוספת ל'אחזור'." "את ההרשאות המוגדרות. מערך התצוגה כולל גם תצורת הרשאה נוספת ל'אחזור'."

View file

@ -1,13 +1,9 @@
# EVIBES GETTEXT TRANSLATIONS
# Copyright (C) 2025 EGOR <FUREUNOIR> GORBUNOV
# This file is distributed under the same license as the EVIBES package.
# EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>, 2025.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2025.4\n" "Project-Id-Version: SCHON 2026.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-10 21:44+0300\n" "POT-Creation-Date: 2026-03-05 16:16+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
@ -16,6 +12,10 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: engine/blog/admin.py:49
msgid "picture preview"
msgstr ""
#: engine/blog/apps.py:8 #: engine/blog/apps.py:8
msgid "blog" msgid "blog"
msgstr "" msgstr ""
@ -28,7 +28,7 @@ msgstr ""
msgid "retrieve a single post (read-only)" msgid "retrieve a single post (read-only)"
msgstr "" msgstr ""
#: engine/blog/models.py:13 #: engine/blog/models.py:22
msgid "" msgid ""
"Represents a blog post model. The Post class defines the structure and " "Represents a blog post model. The Post class defines the structure and "
"behavior of a blog post. It includes attributes for author, title, content, " "behavior of a blog post. It includes attributes for author, title, content, "
@ -38,15 +38,23 @@ msgid ""
"title." "title."
msgstr "" msgstr ""
#: engine/blog/models.py:24 #: engine/blog/models.py:43
msgid "post title" msgid "post title"
msgstr "" msgstr ""
#: engine/blog/models.py:24 #: engine/blog/models.py:44
msgid "title" msgid "title"
msgstr "" msgstr ""
#: engine/blog/models.py:69 #: engine/blog/models.py:47
msgid "content"
msgstr ""
#: engine/blog/models.py:48
msgid "post content"
msgstr ""
#: engine/blog/models.py:68
msgid "is static page" msgid "is static page"
msgstr "" msgstr ""
@ -54,44 +62,43 @@ msgstr ""
msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?" msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?"
msgstr "" msgstr ""
#: engine/blog/models.py:77 #: engine/blog/models.py:82
msgid "post" msgid "post"
msgstr "" msgstr ""
#: engine/blog/models.py:78 #: engine/blog/models.py:83
msgid "posts" msgid "posts"
msgstr "" msgstr ""
#: engine/blog/models.py:82 #: engine/blog/models.py:88
msgid "markdown files are not supported yet - use markdown content instead" msgid "file uploads are not supported yet - use content instead"
msgstr "" msgstr ""
#: engine/blog/models.py:84 #: engine/blog/models.py:92
msgid "" msgid "a file or content must be provided - mutually exclusive"
"a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
#: engine/blog/models.py:116 #: engine/blog/models.py:125
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "" msgstr ""
#: engine/blog/models.py:117 #: engine/blog/models.py:126
msgid "tag name" msgid "tag name"
msgstr "" msgstr ""
#: engine/blog/models.py:121 #: engine/blog/models.py:130
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "" msgstr ""
#: engine/blog/models.py:122 #: engine/blog/models.py:131
msgid "tag display name" msgid "tag display name"
msgstr "" msgstr ""
#: engine/blog/models.py:130 #: engine/blog/models.py:139
msgid "post tag" msgid "post tag"
msgstr "" msgstr ""
#: engine/blog/models.py:131 #: engine/blog/models.py:140
msgid "post tags" msgid "post tags"
msgstr "" msgstr ""

View file

@ -5,9 +5,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: SCHON 2026.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-10 21:44+0300\n" "POT-Creation-Date: 2026-03-05 16:16+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -16,6 +16,10 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: engine/blog/admin.py:49
msgid "picture preview"
msgstr ""
#: engine/blog/apps.py:8 #: engine/blog/apps.py:8
msgid "blog" msgid "blog"
msgstr "" msgstr ""
@ -28,7 +32,7 @@ msgstr ""
msgid "retrieve a single post (read-only)" msgid "retrieve a single post (read-only)"
msgstr "" msgstr ""
#: engine/blog/models.py:13 #: engine/blog/models.py:22
msgid "" msgid ""
"Represents a blog post model. The Post class defines the structure and " "Represents a blog post model. The Post class defines the structure and "
"behavior of a blog post. It includes attributes for author, title, content, " "behavior of a blog post. It includes attributes for author, title, content, "
@ -38,15 +42,23 @@ msgid ""
"title." "title."
msgstr "" msgstr ""
#: engine/blog/models.py:24 #: engine/blog/models.py:43
msgid "post title" msgid "post title"
msgstr "" msgstr ""
#: engine/blog/models.py:24 #: engine/blog/models.py:44
msgid "title" msgid "title"
msgstr "" msgstr ""
#: engine/blog/models.py:69 #: engine/blog/models.py:47
msgid "content"
msgstr ""
#: engine/blog/models.py:48
msgid "post content"
msgstr ""
#: engine/blog/models.py:68
msgid "is static page" msgid "is static page"
msgstr "" msgstr ""
@ -54,44 +66,43 @@ msgstr ""
msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?" msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?"
msgstr "" msgstr ""
#: engine/blog/models.py:77 #: engine/blog/models.py:82
msgid "post" msgid "post"
msgstr "" msgstr ""
#: engine/blog/models.py:78 #: engine/blog/models.py:83
msgid "posts" msgid "posts"
msgstr "" msgstr ""
#: engine/blog/models.py:82 #: engine/blog/models.py:88
msgid "markdown files are not supported yet - use markdown content instead" msgid "file uploads are not supported yet - use content instead"
msgstr "" msgstr ""
#: engine/blog/models.py:84 #: engine/blog/models.py:92
msgid "" msgid "a file or content must be provided - mutually exclusive"
"a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
#: engine/blog/models.py:116 #: engine/blog/models.py:125
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "" msgstr ""
#: engine/blog/models.py:117 #: engine/blog/models.py:126
msgid "tag name" msgid "tag name"
msgstr "" msgstr ""
#: engine/blog/models.py:121 #: engine/blog/models.py:130
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "" msgstr ""
#: engine/blog/models.py:122 #: engine/blog/models.py:131
msgid "tag display name" msgid "tag display name"
msgstr "" msgstr ""
#: engine/blog/models.py:130 #: engine/blog/models.py:139
msgid "post tag" msgid "post tag"
msgstr "" msgstr ""
#: engine/blog/models.py:131 #: engine/blog/models.py:140
msgid "post tags" msgid "post tags"
msgstr "" msgstr ""

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2025.4\n" "Project-Id-Version: SCHON 2026.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-10 21:44+0300\n" "POT-Creation-Date: 2026-03-05 16:16+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
@ -13,6 +13,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: engine/blog/admin.py:49
msgid "picture preview"
msgstr "Pratinjau gambar"
#: engine/blog/apps.py:8 #: engine/blog/apps.py:8
msgid "blog" msgid "blog"
msgstr "Blog" msgstr "Blog"
@ -25,82 +29,89 @@ msgstr "Daftar semua postingan (hanya-baca)"
msgid "retrieve a single post (read-only)" msgid "retrieve a single post (read-only)"
msgstr "Mengambil satu postingan (hanya-baca)" msgstr "Mengambil satu postingan (hanya-baca)"
#: engine/blog/models.py:13 #: engine/blog/models.py:22
msgid "" msgid ""
"Represents a blog post model. The Post class defines the structure and " "Represents a blog post model. The Post class defines the structure and "
"behavior of a blog post. It includes attributes for author, title, content, " "behavior of a blog post. It includes attributes for author, title, content, "
"optional file attachment, slug, and associated tags. The class enforces " "optional file attachment, slug, and associated tags. The class enforces "
"constraints such as requiring either content or a file attachment but not " "constraints such as requiring either content or a file attachment but not "
"both simultaneously. It also supports automatic slug generation based on the " "both simultaneously. It also supports automatic slug generation based on the"
"title." " title."
msgstr "" msgstr ""
"Mewakili model posting blog. Kelas Post mendefinisikan struktur dan perilaku " "Mewakili model posting blog. Kelas Post mendefinisikan struktur dan perilaku"
"postingan blog. Kelas ini mencakup atribut untuk penulis, judul, konten, " " postingan blog. Kelas ini mencakup atribut untuk penulis, judul, konten, "
"lampiran file opsional, slug, dan tag yang terkait. Kelas ini memberlakukan " "lampiran file opsional, slug, dan tag yang terkait. Kelas ini memberlakukan "
"batasan seperti membutuhkan konten atau lampiran file, tetapi tidak keduanya " "batasan seperti membutuhkan konten atau lampiran file, tetapi tidak keduanya"
"secara bersamaan. Kelas ini juga mendukung pembuatan slug otomatis " " secara bersamaan. Kelas ini juga mendukung pembuatan slug otomatis "
"berdasarkan judul." "berdasarkan judul."
#: engine/blog/models.py:24 #: engine/blog/models.py:43
msgid "post title" msgid "post title"
msgstr "Judul postingan" msgstr "Judul postingan"
#: engine/blog/models.py:24 #: engine/blog/models.py:44
msgid "title" msgid "title"
msgstr "Judul" msgstr "Judul"
#: engine/blog/models.py:69 #: engine/blog/models.py:47
msgid "content"
msgstr "konten"
#: engine/blog/models.py:48
msgid "post content"
msgstr "posting konten"
#: engine/blog/models.py:68
msgid "is static page" msgid "is static page"
msgstr "adalah halaman statis" msgstr "adalah halaman statis"
#: engine/blog/models.py:70 #: engine/blog/models.py:70
msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?" msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?"
msgstr "" msgstr ""
"apakah ini adalah postingan untuk halaman dengan URL statis (misalnya `/help/" "apakah ini adalah postingan untuk halaman dengan URL statis (misalnya "
"pengiriman`)?" "`/help/pengiriman`)?"
#: engine/blog/models.py:77 #: engine/blog/models.py:82
msgid "post" msgid "post"
msgstr "Pos" msgstr "Pos"
#: engine/blog/models.py:78 #: engine/blog/models.py:83
msgid "posts" msgid "posts"
msgstr "Posting" msgstr "Posting"
#: engine/blog/models.py:82 #: engine/blog/models.py:88
msgid "markdown files are not supported yet - use markdown content instead" msgid "file uploads are not supported yet - use content instead"
msgstr "" msgstr ""
"File penurunan harga tidak didukung - gunakan konten penurunan harga sebagai " "File penurunan harga tidak didukung - gunakan konten penurunan harga sebagai"
"gantinya!" " gantinya!"
#: engine/blog/models.py:84 #: engine/blog/models.py:92
msgid "" msgid "a file or content must be provided - mutually exclusive"
"a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"file penurunan harga atau konten penurunan harga harus disediakan - tidak " "file penurunan harga atau konten penurunan harga harus disediakan - tidak "
"boleh ada yang sama" "boleh ada yang sama"
#: engine/blog/models.py:116 #: engine/blog/models.py:125
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "pengidentifikasi tag internal untuk tag pos" msgstr "pengidentifikasi tag internal untuk tag pos"
#: engine/blog/models.py:117 #: engine/blog/models.py:126
msgid "tag name" msgid "tag name"
msgstr "Nama tag" msgstr "Nama tag"
#: engine/blog/models.py:121 #: engine/blog/models.py:130
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "Nama yang mudah digunakan untuk tag postingan" msgstr "Nama yang mudah digunakan untuk tag postingan"
#: engine/blog/models.py:122 #: engine/blog/models.py:131
msgid "tag display name" msgid "tag display name"
msgstr "Nama tampilan tag" msgstr "Nama tampilan tag"
#: engine/blog/models.py:130 #: engine/blog/models.py:139
msgid "post tag" msgid "post tag"
msgstr "Tag pos" msgstr "Tag pos"
#: engine/blog/models.py:131 #: engine/blog/models.py:140
msgid "post tags" msgid "post tags"
msgstr "Tag pos" msgstr "Tag pos"
@ -113,8 +124,8 @@ msgid ""
"defined permissions. The view set also includes an additional 'retrieve' " "defined permissions. The view set also includes an additional 'retrieve' "
"permission configuration." "permission configuration."
msgstr "" msgstr ""
"Mengenkapsulasi operasi untuk mengelola dan mengambil entitas Post dalam set " "Mengenkapsulasi operasi untuk mengelola dan mengambil entitas Post dalam set"
"tampilan model hanya-baca. Kelas ini dirancang untuk menangani objek Post " " tampilan model hanya-baca. Kelas ini dirancang untuk menangani objek Post "
"yang aktif dan memungkinkan penyaringan berdasarkan filter yang ditentukan. " "yang aktif dan memungkinkan penyaringan berdasarkan filter yang ditentukan. "
"Kelas ini terintegrasi dengan sistem penyaringan backend Django dan " "Kelas ini terintegrasi dengan sistem penyaringan backend Django dan "
"memastikan operasi sesuai dengan izin yang ditentukan. Kumpulan view juga " "memastikan operasi sesuai dengan izin yang ditentukan. Kumpulan view juga "

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2025.4\n" "Project-Id-Version: SCHON 2026.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-10 21:44+0300\n" "POT-Creation-Date: 2026-03-05 16:16+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
@ -13,6 +13,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: engine/blog/admin.py:49
msgid "picture preview"
msgstr "Anteprima immagine"
#: engine/blog/apps.py:8 #: engine/blog/apps.py:8
msgid "blog" msgid "blog"
msgstr "Blog" msgstr "Blog"
@ -25,80 +29,89 @@ msgstr "Elenco di tutti i messaggi (solo lettura)"
msgid "retrieve a single post (read-only)" msgid "retrieve a single post (read-only)"
msgstr "Recuperare un singolo post (solo lettura)" msgstr "Recuperare un singolo post (solo lettura)"
#: engine/blog/models.py:13 #: engine/blog/models.py:22
msgid "" msgid ""
"Represents a blog post model. The Post class defines the structure and " "Represents a blog post model. The Post class defines the structure and "
"behavior of a blog post. It includes attributes for author, title, content, " "behavior of a blog post. It includes attributes for author, title, content, "
"optional file attachment, slug, and associated tags. The class enforces " "optional file attachment, slug, and associated tags. The class enforces "
"constraints such as requiring either content or a file attachment but not " "constraints such as requiring either content or a file attachment but not "
"both simultaneously. It also supports automatic slug generation based on the " "both simultaneously. It also supports automatic slug generation based on the"
"title." " title."
msgstr "" msgstr ""
"Rappresenta il modello di un post di un blog. La classe Post definisce la " "Rappresenta il modello di un post di un blog. La classe Post definisce la "
"struttura e il comportamento di un post del blog. Include gli attributi per " "struttura e il comportamento di un post del blog. Include gli attributi per "
"l'autore, il titolo, il contenuto, l'allegato opzionale, lo slug e i tag " "l'autore, il titolo, il contenuto, l'allegato opzionale, lo slug e i tag "
"associati. La classe impone dei vincoli, come la richiesta di un contenuto o " "associati. La classe impone dei vincoli, come la richiesta di un contenuto o"
"di un file allegato, ma non di entrambi contemporaneamente. Supporta anche " " di un file allegato, ma non di entrambi contemporaneamente. Supporta anche "
"la generazione automatica dello slug in base al titolo." "la generazione automatica dello slug in base al titolo."
#: engine/blog/models.py:24 #: engine/blog/models.py:43
msgid "post title" msgid "post title"
msgstr "Titolo del post" msgstr "Titolo del post"
#: engine/blog/models.py:24 #: engine/blog/models.py:44
msgid "title" msgid "title"
msgstr "Titolo" msgstr "Titolo"
#: engine/blog/models.py:69 #: engine/blog/models.py:47
msgid "content"
msgstr "contenuto"
#: engine/blog/models.py:48
msgid "post content"
msgstr "pubblicare contenuti"
#: engine/blog/models.py:68
msgid "is static page" msgid "is static page"
msgstr "è una pagina statica" msgstr "è una pagina statica"
#: engine/blog/models.py:70 #: engine/blog/models.py:70
msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?" msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?"
msgstr "" msgstr ""
"Si tratta di un post per una pagina con URL statico (ad esempio `/help/" "Si tratta di un post per una pagina con URL statico (ad esempio "
"delivery`)?" "`/help/delivery`)?"
#: engine/blog/models.py:77 #: engine/blog/models.py:82
msgid "post" msgid "post"
msgstr "Posta" msgstr "Posta"
#: engine/blog/models.py:78 #: engine/blog/models.py:83
msgid "posts" msgid "posts"
msgstr "Messaggi" msgstr "Messaggi"
#: engine/blog/models.py:82 #: engine/blog/models.py:88
msgid "markdown files are not supported yet - use markdown content instead" msgid "file uploads are not supported yet - use content instead"
msgstr "I file Markdown non sono supportati: usa invece i contenuti Markdown!" msgstr ""
"Il caricamento dei file non è ancora supportato: utilizzare invece i "
"contenuti."
#: engine/blog/models.py:84 #: engine/blog/models.py:92
msgid "" msgid "a file or content must be provided - mutually exclusive"
"a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"deve essere fornito un file markdown o un contenuto markdown - si escludono " "deve essere fornito un file markdown o un contenuto markdown - si escludono "
"a vicenda" "a vicenda"
#: engine/blog/models.py:116 #: engine/blog/models.py:125
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "identificatore interno del tag post" msgstr "identificatore interno del tag post"
#: engine/blog/models.py:117 #: engine/blog/models.py:126
msgid "tag name" msgid "tag name"
msgstr "Nome del tag" msgstr "Nome del tag"
#: engine/blog/models.py:121 #: engine/blog/models.py:130
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "Nome intuitivo per il tag del post" msgstr "Nome intuitivo per il tag del post"
#: engine/blog/models.py:122 #: engine/blog/models.py:131
msgid "tag display name" msgid "tag display name"
msgstr "Nome del tag" msgstr "Nome del tag"
#: engine/blog/models.py:130 #: engine/blog/models.py:139
msgid "post tag" msgid "post tag"
msgstr "Post tag" msgstr "Post tag"
#: engine/blog/models.py:131 #: engine/blog/models.py:140
msgid "post tags" msgid "post tags"
msgstr "Tag dei post" msgstr "Tag dei post"
@ -113,8 +126,8 @@ msgid ""
msgstr "" msgstr ""
"Incapsula le operazioni per gestire e recuperare le entità Post in un " "Incapsula le operazioni per gestire e recuperare le entità Post in un "
"insieme di viste del modello di sola lettura. Questa classe è fatta su " "insieme di viste del modello di sola lettura. Questa classe è fatta su "
"misura per gestire gli oggetti Post che sono attivi e consente il filtraggio " "misura per gestire gli oggetti Post che sono attivi e consente il filtraggio"
"in base ai filtri definiti. Si integra con il sistema di filtraggio del " " in base ai filtri definiti. Si integra con il sistema di filtraggio del "
"backend di Django e garantisce che le operazioni siano in linea con i " "backend di Django e garantisce che le operazioni siano in linea con i "
"permessi definiti. L'insieme di viste include anche un'ulteriore " "permessi definiti. L'insieme di viste include anche un'ulteriore "
"configurazione di permessi 'retrieve'." "configurazione di permessi 'retrieve'."

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2025.4\n" "Project-Id-Version: SCHON 2026.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-10 21:44+0300\n" "POT-Creation-Date: 2026-03-05 16:16+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
@ -13,6 +13,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: engine/blog/admin.py:49
msgid "picture preview"
msgstr "画像プレビュー"
#: engine/blog/apps.py:8 #: engine/blog/apps.py:8
msgid "blog" msgid "blog"
msgstr "ブログ" msgstr "ブログ"
@ -25,30 +29,35 @@ msgstr "すべての投稿をリストアップする(読み取り専用)"
msgid "retrieve a single post (read-only)" msgid "retrieve a single post (read-only)"
msgstr "単一の投稿を取得する(読み取り専用)" msgstr "単一の投稿を取得する(読み取り専用)"
#: engine/blog/models.py:13 #: engine/blog/models.py:22
msgid "" msgid ""
"Represents a blog post model. The Post class defines the structure and " "Represents a blog post model. The Post class defines the structure and "
"behavior of a blog post. It includes attributes for author, title, content, " "behavior of a blog post. It includes attributes for author, title, content, "
"optional file attachment, slug, and associated tags. The class enforces " "optional file attachment, slug, and associated tags. The class enforces "
"constraints such as requiring either content or a file attachment but not " "constraints such as requiring either content or a file attachment but not "
"both simultaneously. It also supports automatic slug generation based on the " "both simultaneously. It also supports automatic slug generation based on the"
"title." " title."
msgstr "" msgstr ""
"ブログ記事のモデルを表します。Post クラスはブログ記事の構造と動作を定義しま" "ブログ記事のモデルを表します。Post "
"す。著者、タイトル、内容、オプションの添付ファイル、スラッグ、関連タグの属性" "クラスはブログ記事の構造と動作を定義します。著者、タイトル、内容、オプションの添付ファイル、スラッグ、関連タグの属性を含みます。このクラスは、内容か添付ファイルのどちらかを要求するが、両方は同時に要求しないといった制約を強制します。また、タイトルに基づくスラッグの自動生成もサポートしています。"
"を含みます。このクラスは、内容か添付ファイルのどちらかを要求するが、両方は同"
"時に要求しないといった制約を強制します。また、タイトルに基づくスラッグの自動"
"生成もサポートしています。"
#: engine/blog/models.py:24 #: engine/blog/models.py:43
msgid "post title" msgid "post title"
msgstr "投稿タイトル" msgstr "投稿タイトル"
#: engine/blog/models.py:24 #: engine/blog/models.py:44
msgid "title" msgid "title"
msgstr "タイトル" msgstr "タイトル"
#: engine/blog/models.py:69 #: engine/blog/models.py:47
msgid "content"
msgstr "内容"
#: engine/blog/models.py:48
msgid "post content"
msgstr "投稿内容"
#: engine/blog/models.py:68
msgid "is static page" msgid "is static page"
msgstr "は静的ページ" msgstr "は静的ページ"
@ -56,48 +65,43 @@ msgstr "は静的ページ"
msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?" msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?"
msgstr "これは静的URLのページ`/help/delivery`)への投稿ですか?" msgstr "これは静的URLのページ`/help/delivery`)への投稿ですか?"
#: engine/blog/models.py:77 #: engine/blog/models.py:82
msgid "post" msgid "post"
msgstr "ポスト" msgstr "ポスト"
#: engine/blog/models.py:78 #: engine/blog/models.py:83
msgid "posts" msgid "posts"
msgstr "投稿" msgstr "投稿"
#: engine/blog/models.py:82 #: engine/blog/models.py:88
msgid "markdown files are not supported yet - use markdown content instead" msgid "file uploads are not supported yet - use content instead"
msgstr "" msgstr "マークダウン・ファイルはサポートされていません - 代わりにマークダウン・コンテンツを使用してください!"
"マークダウン・ファイルはサポートされていません - 代わりにマークダウン・コンテ"
"ンツを使用してください!"
#: engine/blog/models.py:84 #: engine/blog/models.py:92
msgid "" msgid "a file or content must be provided - mutually exclusive"
"a markdown file or markdown content must be provided - mutually exclusive" msgstr "マークダウン・ファイルまたはマークダウン・コンテンツを提供しなければならない。"
msgstr ""
"マークダウン・ファイルまたはマークダウン・コンテンツを提供しなければならな"
"い。"
#: engine/blog/models.py:116 #: engine/blog/models.py:125
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "投稿タグの内部タグ識別子" msgstr "投稿タグの内部タグ識別子"
#: engine/blog/models.py:117 #: engine/blog/models.py:126
msgid "tag name" msgid "tag name"
msgstr "タグ名" msgstr "タグ名"
#: engine/blog/models.py:121 #: engine/blog/models.py:130
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "投稿タグのユーザーフレンドリーな名前" msgstr "投稿タグのユーザーフレンドリーな名前"
#: engine/blog/models.py:122 #: engine/blog/models.py:131
msgid "tag display name" msgid "tag display name"
msgstr "タグ表示名" msgstr "タグ表示名"
#: engine/blog/models.py:130 #: engine/blog/models.py:139
msgid "post tag" msgid "post tag"
msgstr "投稿タグ" msgstr "投稿タグ"
#: engine/blog/models.py:131 #: engine/blog/models.py:140
msgid "post tags" msgid "post tags"
msgstr "投稿タグ" msgstr "投稿タグ"
@ -110,9 +114,7 @@ msgid ""
"defined permissions. The view set also includes an additional 'retrieve' " "defined permissions. The view set also includes an additional 'retrieve' "
"permission configuration." "permission configuration."
msgstr "" msgstr ""
"読み取り専用のモデルビューセットにおける Post エンティティの管理と取得のため" "読み取り専用のモデルビューセットにおける Post エンティティの管理と取得のための操作をカプセル化します。このクラスは、アクティブな Post "
"の操作をカプセル化します。このクラスは、アクティブな Post オブジェクトを扱" "オブジェクトを扱い、定義されたフィルタに基 づいてフィルタリングできるように調整されています。Django "
"い、定義されたフィルタに基 づいてフィルタリングできるように調整されています。" "のバックエンドのフィルタリングシステムと統合し、定義されたパーミッションに沿った操作を保証します。ビューセットには、追加の 'retrieve' "
"Django のバックエンドのフィルタリングシステムと統合し、定義されたパーミッショ" "権限設定も含まれます。"
"ンに沿った操作を保証します。ビューセットには、追加の 'retrieve' 権限設定も含"
"まれます。"

View file

@ -1,13 +1,9 @@
# EVIBES GETTEXT TRANSLATIONS
# Copyright (C) 2025 EGOR <FUREUNOIR> GORBUNOV
# This file is distributed under the same license as the EVIBES package.
# EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>, 2025.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2025.4\n" "Project-Id-Version: SCHON 2026.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-10 21:44+0300\n" "POT-Creation-Date: 2026-03-05 16:16+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
@ -16,6 +12,10 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: engine/blog/admin.py:49
msgid "picture preview"
msgstr ""
#: engine/blog/apps.py:8 #: engine/blog/apps.py:8
msgid "blog" msgid "blog"
msgstr "" msgstr ""
@ -28,7 +28,7 @@ msgstr ""
msgid "retrieve a single post (read-only)" msgid "retrieve a single post (read-only)"
msgstr "" msgstr ""
#: engine/blog/models.py:13 #: engine/blog/models.py:22
msgid "" msgid ""
"Represents a blog post model. The Post class defines the structure and " "Represents a blog post model. The Post class defines the structure and "
"behavior of a blog post. It includes attributes for author, title, content, " "behavior of a blog post. It includes attributes for author, title, content, "
@ -38,15 +38,23 @@ msgid ""
"title." "title."
msgstr "" msgstr ""
#: engine/blog/models.py:24 #: engine/blog/models.py:43
msgid "post title" msgid "post title"
msgstr "" msgstr ""
#: engine/blog/models.py:24 #: engine/blog/models.py:44
msgid "title" msgid "title"
msgstr "" msgstr ""
#: engine/blog/models.py:69 #: engine/blog/models.py:47
msgid "content"
msgstr ""
#: engine/blog/models.py:48
msgid "post content"
msgstr ""
#: engine/blog/models.py:68
msgid "is static page" msgid "is static page"
msgstr "" msgstr ""
@ -54,44 +62,43 @@ msgstr ""
msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?" msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?"
msgstr "" msgstr ""
#: engine/blog/models.py:77 #: engine/blog/models.py:82
msgid "post" msgid "post"
msgstr "" msgstr ""
#: engine/blog/models.py:78 #: engine/blog/models.py:83
msgid "posts" msgid "posts"
msgstr "" msgstr ""
#: engine/blog/models.py:82 #: engine/blog/models.py:88
msgid "markdown files are not supported yet - use markdown content instead" msgid "file uploads are not supported yet - use content instead"
msgstr "" msgstr ""
#: engine/blog/models.py:84 #: engine/blog/models.py:92
msgid "" msgid "a file or content must be provided - mutually exclusive"
"a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
#: engine/blog/models.py:116 #: engine/blog/models.py:125
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "" msgstr ""
#: engine/blog/models.py:117 #: engine/blog/models.py:126
msgid "tag name" msgid "tag name"
msgstr "" msgstr ""
#: engine/blog/models.py:121 #: engine/blog/models.py:130
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "" msgstr ""
#: engine/blog/models.py:122 #: engine/blog/models.py:131
msgid "tag display name" msgid "tag display name"
msgstr "" msgstr ""
#: engine/blog/models.py:130 #: engine/blog/models.py:139
msgid "post tag" msgid "post tag"
msgstr "" msgstr ""
#: engine/blog/models.py:131 #: engine/blog/models.py:140
msgid "post tags" msgid "post tags"
msgstr "" msgstr ""

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2025.4\n" "Project-Id-Version: SCHON 2026.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-10 21:44+0300\n" "POT-Creation-Date: 2026-03-05 16:16+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
@ -13,6 +13,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: engine/blog/admin.py:49
msgid "picture preview"
msgstr "사진 미리보기"
#: engine/blog/apps.py:8 #: engine/blog/apps.py:8
msgid "blog" msgid "blog"
msgstr "블로그" msgstr "블로그"
@ -25,30 +29,36 @@ msgstr "모든 게시물 나열(읽기 전용)"
msgid "retrieve a single post (read-only)" msgid "retrieve a single post (read-only)"
msgstr "단일 게시물 검색(읽기 전용)" msgstr "단일 게시물 검색(읽기 전용)"
#: engine/blog/models.py:13 #: engine/blog/models.py:22
msgid "" msgid ""
"Represents a blog post model. The Post class defines the structure and " "Represents a blog post model. The Post class defines the structure and "
"behavior of a blog post. It includes attributes for author, title, content, " "behavior of a blog post. It includes attributes for author, title, content, "
"optional file attachment, slug, and associated tags. The class enforces " "optional file attachment, slug, and associated tags. The class enforces "
"constraints such as requiring either content or a file attachment but not " "constraints such as requiring either content or a file attachment but not "
"both simultaneously. It also supports automatic slug generation based on the " "both simultaneously. It also supports automatic slug generation based on the"
"title." " title."
msgstr "" msgstr ""
"블로그 글 모델을 나타냅니다. Post 클래스는 블로그 글의 구조와 동작을 정의합니" "블로그 글 모델을 나타냅니다. Post 클래스는 블로그 글의 구조와 동작을 정의합니다. 여기에는 작성자, 제목, 콘텐츠, 선택적 파일 "
"다. 여기에는 작성자, 제목, 콘텐츠, 선택적 파일 첨부, 슬러그 및 관련 태그에 대" "첨부, 슬러그 및 관련 태그에 대한 속성이 포함됩니다. 이 클래스는 콘텐츠 또는 파일 첨부 중 하나만 요구하고 둘 다 동시에 요구하지 "
"한 속성이 포함됩니다. 이 클래스는 콘텐츠 또는 파일 첨부 중 하나만 요구하고 " "않는 등의 제약 조건을 적용합니다. 또한 제목에 따른 자동 슬러그 생성도 지원합니다."
"둘 다 동시에 요구하지 않는 등의 제약 조건을 적용합니다. 또한 제목에 따른 자"
"동 슬러그 생성도 지원합니다."
#: engine/blog/models.py:24 #: engine/blog/models.py:43
msgid "post title" msgid "post title"
msgstr "게시물 제목" msgstr "게시물 제목"
#: engine/blog/models.py:24 #: engine/blog/models.py:44
msgid "title" msgid "title"
msgstr "제목" msgstr "제목"
#: engine/blog/models.py:69 #: engine/blog/models.py:47
msgid "content"
msgstr "콘텐츠"
#: engine/blog/models.py:48
msgid "post content"
msgstr "게시물 내용"
#: engine/blog/models.py:68
msgid "is static page" msgid "is static page"
msgstr "는 정적 페이지입니다." msgstr "는 정적 페이지입니다."
@ -56,45 +66,43 @@ msgstr "는 정적 페이지입니다."
msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?" msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?"
msgstr "정적 URL(예: `/help/delivery`)이 있는 페이지의 게시물인가요?" msgstr "정적 URL(예: `/help/delivery`)이 있는 페이지의 게시물인가요?"
#: engine/blog/models.py:77 #: engine/blog/models.py:82
msgid "post" msgid "post"
msgstr "게시물" msgstr "게시물"
#: engine/blog/models.py:78 #: engine/blog/models.py:83
msgid "posts" msgid "posts"
msgstr "게시물" msgstr "게시물"
#: engine/blog/models.py:82 #: engine/blog/models.py:88
msgid "markdown files are not supported yet - use markdown content instead" msgid "file uploads are not supported yet - use content instead"
msgstr "" msgstr "마크다운 파일은 지원되지 않습니다 예 - 대신 마크다운 콘텐츠를 사용하세요!"
"마크다운 파일은 지원되지 않습니다 예 - 대신 마크다운 콘텐츠를 사용하세요!"
#: engine/blog/models.py:84 #: engine/blog/models.py:92
msgid "" msgid "a file or content must be provided - mutually exclusive"
"a markdown file or markdown content must be provided - mutually exclusive" msgstr "마크다운 파일 또는 마크다운 콘텐츠를 제공해야 합니다 - 상호 배타적"
msgstr "마크다운 파일 또는 마크다운 콘텐츠가 제공되어야 합니다."
#: engine/blog/models.py:116 #: engine/blog/models.py:125
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "게시물 태그의 내부 태그 식별자" msgstr "게시물 태그의 내부 태그 식별자"
#: engine/blog/models.py:117 #: engine/blog/models.py:126
msgid "tag name" msgid "tag name"
msgstr "태그 이름" msgstr "태그 이름"
#: engine/blog/models.py:121 #: engine/blog/models.py:130
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "게시물 태그의 사용자 친화적인 이름" msgstr "게시물 태그의 사용자 친화적인 이름"
#: engine/blog/models.py:122 #: engine/blog/models.py:131
msgid "tag display name" msgid "tag display name"
msgstr "태그 표시 이름" msgstr "태그 표시 이름"
#: engine/blog/models.py:130 #: engine/blog/models.py:139
msgid "post tag" msgid "post tag"
msgstr "게시물 태그" msgstr "게시물 태그"
#: engine/blog/models.py:131 #: engine/blog/models.py:140
msgid "post tags" msgid "post tags"
msgstr "게시물 태그" msgstr "게시물 태그"
@ -107,8 +115,6 @@ msgid ""
"defined permissions. The view set also includes an additional 'retrieve' " "defined permissions. The view set also includes an additional 'retrieve' "
"permission configuration." "permission configuration."
msgstr "" msgstr ""
"읽기 전용 모델 보기 집합에서 Post 엔티티를 관리하고 검색하기 위한 작업을 캡슐" "읽기 전용 모델 보기 집합에서 Post 엔티티를 관리하고 검색하기 위한 작업을 캡슐화합니다. 이 클래스는 활성 상태인 Post 개체를 "
"화합니다. 이 클래스는 활성 상태인 Post 개체를 처리하도록 맞춤화되어 있으며 정" "처리하도록 맞춤화되어 있으며 정의된 필터를 기반으로 필터링을 허용합니다. 이 클래스는 장고의 백엔드 필터링 시스템과 통합되며 정의된 "
"의된 필터를 기반으로 필터링을 허용합니다. 이 클래스는 장고의 백엔드 필터링 시" "권한에 따라 작업이 이루어지도록 합니다. 보기 세트에는 추가 '검색' 권한 구성도 포함되어 있습니다."
"스템과 통합되며 정의된 권한에 따라 작업이 이루어지도록 합니다. 보기 세트에는 "
"추가 '검색' 권한 구성도 포함되어 있습니다."

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2025.4\n" "Project-Id-Version: SCHON 2026.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-10 21:44+0300\n" "POT-Creation-Date: 2026-03-05 16:16+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
@ -13,6 +13,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: engine/blog/admin.py:49
msgid "picture preview"
msgstr "Afbeelding preview"
#: engine/blog/apps.py:8 #: engine/blog/apps.py:8
msgid "blog" msgid "blog"
msgstr "Blog" msgstr "Blog"
@ -25,14 +29,14 @@ msgstr "Alle berichten weergeven (alleen-lezen)"
msgid "retrieve a single post (read-only)" msgid "retrieve a single post (read-only)"
msgstr "Een enkel bericht ophalen (alleen-lezen)" msgstr "Een enkel bericht ophalen (alleen-lezen)"
#: engine/blog/models.py:13 #: engine/blog/models.py:22
msgid "" msgid ""
"Represents a blog post model. The Post class defines the structure and " "Represents a blog post model. The Post class defines the structure and "
"behavior of a blog post. It includes attributes for author, title, content, " "behavior of a blog post. It includes attributes for author, title, content, "
"optional file attachment, slug, and associated tags. The class enforces " "optional file attachment, slug, and associated tags. The class enforces "
"constraints such as requiring either content or a file attachment but not " "constraints such as requiring either content or a file attachment but not "
"both simultaneously. It also supports automatic slug generation based on the " "both simultaneously. It also supports automatic slug generation based on the"
"title." " title."
msgstr "" msgstr ""
"Vertegenwoordigt een blogpostmodel. De klasse Post definieert de structuur " "Vertegenwoordigt een blogpostmodel. De klasse Post definieert de structuur "
"en het gedrag van een blogbericht. Ze bevat attributen voor auteur, titel, " "en het gedrag van een blogbericht. Ze bevat attributen voor auteur, titel, "
@ -41,66 +45,73 @@ msgstr ""
"maar niet beide tegelijk. Het ondersteunt ook het automatisch genereren van " "maar niet beide tegelijk. Het ondersteunt ook het automatisch genereren van "
"slugs op basis van de titel." "slugs op basis van de titel."
#: engine/blog/models.py:24 #: engine/blog/models.py:43
msgid "post title" msgid "post title"
msgstr "Titel van de post" msgstr "Titel van de post"
#: engine/blog/models.py:24 #: engine/blog/models.py:44
msgid "title" msgid "title"
msgstr "Titel" msgstr "Titel"
#: engine/blog/models.py:69 #: engine/blog/models.py:47
msgid "content"
msgstr "inhoud"
#: engine/blog/models.py:48
msgid "post content"
msgstr "berichtinhoud"
#: engine/blog/models.py:68
msgid "is static page" msgid "is static page"
msgstr "is statische pagina" msgstr "is statische pagina"
#: engine/blog/models.py:70 #: engine/blog/models.py:70
msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?" msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?"
msgstr "" msgstr ""
"Is dit een bericht voor een pagina met een statische URL (bijv. `/help/" "Is dit een bericht voor een pagina met een statische URL (bijv. "
"delivery`)?" "`/help/delivery`)?"
#: engine/blog/models.py:77 #: engine/blog/models.py:82
msgid "post" msgid "post"
msgstr "Plaats" msgstr "Plaats"
#: engine/blog/models.py:78 #: engine/blog/models.py:83
msgid "posts" msgid "posts"
msgstr "Berichten" msgstr "Berichten"
#: engine/blog/models.py:82 #: engine/blog/models.py:88
msgid "markdown files are not supported yet - use markdown content instead" msgid "file uploads are not supported yet - use content instead"
msgstr "" msgstr ""
"Markdown-bestanden worden niet ondersteund - gebruik in plaats daarvan " "Markdown-bestanden worden niet ondersteund - gebruik in plaats daarvan "
"markdown-inhoud!" "markdown-inhoud!"
#: engine/blog/models.py:84 #: engine/blog/models.py:92
msgid "" msgid "a file or content must be provided - mutually exclusive"
"a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"er moet een markdown-bestand of markdown-inhoud worden geleverd - wederzijds " "er moet een markdown-bestand of markdown-inhoud worden geleverd - wederzijds"
"exclusief" " exclusief"
#: engine/blog/models.py:116 #: engine/blog/models.py:125
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "interne tagidentifier voor de posttag" msgstr "interne tagidentifier voor de posttag"
#: engine/blog/models.py:117 #: engine/blog/models.py:126
msgid "tag name" msgid "tag name"
msgstr "Tag naam" msgstr "Tag naam"
#: engine/blog/models.py:121 #: engine/blog/models.py:130
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "Gebruiksvriendelijke naam voor de posttag" msgstr "Gebruiksvriendelijke naam voor de posttag"
#: engine/blog/models.py:122 #: engine/blog/models.py:131
msgid "tag display name" msgid "tag display name"
msgstr "Tag weergavenaam" msgstr "Tag weergavenaam"
#: engine/blog/models.py:130 #: engine/blog/models.py:139
msgid "post tag" msgid "post tag"
msgstr "Post tag" msgstr "Post tag"
#: engine/blog/models.py:131 #: engine/blog/models.py:140
msgid "post tags" msgid "post tags"
msgstr "Post tags" msgstr "Post tags"

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2025.4\n" "Project-Id-Version: SCHON 2026.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-10 21:44+0300\n" "POT-Creation-Date: 2026-03-05 16:16+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
@ -13,6 +13,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: engine/blog/admin.py:49
msgid "picture preview"
msgstr "Bildeforhåndsvisning"
#: engine/blog/apps.py:8 #: engine/blog/apps.py:8
msgid "blog" msgid "blog"
msgstr "Blogg" msgstr "Blogg"
@ -25,14 +29,14 @@ msgstr "Liste over alle innlegg (skrivebeskyttet)"
msgid "retrieve a single post (read-only)" msgid "retrieve a single post (read-only)"
msgstr "Hent et enkelt innlegg (skrivebeskyttet)" msgstr "Hent et enkelt innlegg (skrivebeskyttet)"
#: engine/blog/models.py:13 #: engine/blog/models.py:22
msgid "" msgid ""
"Represents a blog post model. The Post class defines the structure and " "Represents a blog post model. The Post class defines the structure and "
"behavior of a blog post. It includes attributes for author, title, content, " "behavior of a blog post. It includes attributes for author, title, content, "
"optional file attachment, slug, and associated tags. The class enforces " "optional file attachment, slug, and associated tags. The class enforces "
"constraints such as requiring either content or a file attachment but not " "constraints such as requiring either content or a file attachment but not "
"both simultaneously. It also supports automatic slug generation based on the " "both simultaneously. It also supports automatic slug generation based on the"
"title." " title."
msgstr "" msgstr ""
"Representerer en blogginnleggsmodell. Post-klassen definerer strukturen og " "Representerer en blogginnleggsmodell. Post-klassen definerer strukturen og "
"virkemåten til et blogginnlegg. Den inneholder attributter for forfatter, " "virkemåten til et blogginnlegg. Den inneholder attributter for forfatter, "
@ -41,15 +45,23 @@ msgstr ""
"må være med, men ikke begge deler samtidig. Den støtter også automatisk " "må være med, men ikke begge deler samtidig. Den støtter også automatisk "
"generering av slug basert på tittelen." "generering av slug basert på tittelen."
#: engine/blog/models.py:24 #: engine/blog/models.py:43
msgid "post title" msgid "post title"
msgstr "Innleggets tittel" msgstr "Innleggets tittel"
#: engine/blog/models.py:24 #: engine/blog/models.py:44
msgid "title" msgid "title"
msgstr "Title" msgstr "Title"
#: engine/blog/models.py:69 #: engine/blog/models.py:47
msgid "content"
msgstr "innhold"
#: engine/blog/models.py:48
msgid "post content"
msgstr "innleggsinnhold"
#: engine/blog/models.py:68
msgid "is static page" msgid "is static page"
msgstr "er statisk side" msgstr "er statisk side"
@ -58,45 +70,44 @@ msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?"
msgstr "" msgstr ""
"er dette et innlegg for en side med statisk URL (f.eks. `/help/delivery`)?" "er dette et innlegg for en side med statisk URL (f.eks. `/help/delivery`)?"
#: engine/blog/models.py:77 #: engine/blog/models.py:82
msgid "post" msgid "post"
msgstr "Post" msgstr "Post"
#: engine/blog/models.py:78 #: engine/blog/models.py:83
msgid "posts" msgid "posts"
msgstr "Innlegg" msgstr "Innlegg"
#: engine/blog/models.py:82 #: engine/blog/models.py:88
msgid "markdown files are not supported yet - use markdown content instead" msgid "file uploads are not supported yet - use content instead"
msgstr "Markdown-filer støttes ikke - bruk markdown-innhold i stedet!" msgstr "filopplasting støttes ikke ennå bruk innhold i stedet"
#: engine/blog/models.py:84 #: engine/blog/models.py:92
msgid "" msgid "a file or content must be provided - mutually exclusive"
"a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"en markdown-fil eller markdown-innhold må oppgis - gjensidig utelukkende" "en markdown-fil eller markdown-innhold må oppgis - gjensidig utelukkende"
#: engine/blog/models.py:116 #: engine/blog/models.py:125
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "intern tagg-identifikator for innleggstaggen" msgstr "intern tagg-identifikator for innleggstaggen"
#: engine/blog/models.py:117 #: engine/blog/models.py:126
msgid "tag name" msgid "tag name"
msgstr "Tagg navn" msgstr "Tagg navn"
#: engine/blog/models.py:121 #: engine/blog/models.py:130
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "Brukervennlig navn for innleggstaggen" msgstr "Brukervennlig navn for innleggstaggen"
#: engine/blog/models.py:122 #: engine/blog/models.py:131
msgid "tag display name" msgid "tag display name"
msgstr "Visningsnavn for taggen" msgstr "Visningsnavn for taggen"
#: engine/blog/models.py:130 #: engine/blog/models.py:139
msgid "post tag" msgid "post tag"
msgstr "Post tag" msgstr "Post tag"
#: engine/blog/models.py:131 #: engine/blog/models.py:140
msgid "post tags" msgid "post tags"
msgstr "Tagger for innlegg" msgstr "Tagger for innlegg"
@ -114,5 +125,5 @@ msgstr ""
"håndtere Post-objekter som er aktive, og tillater filtrering basert på " "håndtere Post-objekter som er aktive, og tillater filtrering basert på "
"definerte filtre. Den integreres med Djangos backend-filtreringssystem og " "definerte filtre. Den integreres med Djangos backend-filtreringssystem og "
"sørger for at operasjonene er i tråd med de definerte tillatelsene. " "sørger for at operasjonene er i tråd med de definerte tillatelsene. "
"Visningssettet inkluderer også en ekstra konfigurasjon for \"hent\"-" "Visningssettet inkluderer også en ekstra konfigurasjon for "
"tillatelse." "\"hent\"-tillatelse."

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2025.4\n" "Project-Id-Version: SCHON 2026.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-10 21:44+0300\n" "POT-Creation-Date: 2026-03-05 16:16+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
@ -13,6 +13,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: engine/blog/admin.py:49
msgid "picture preview"
msgstr "Podgląd obrazu"
#: engine/blog/apps.py:8 #: engine/blog/apps.py:8
msgid "blog" msgid "blog"
msgstr "Blog" msgstr "Blog"
@ -25,14 +29,14 @@ msgstr "Lista wszystkich postów (tylko do odczytu)"
msgid "retrieve a single post (read-only)" msgid "retrieve a single post (read-only)"
msgstr "Pobieranie pojedynczego wpisu (tylko do odczytu)" msgstr "Pobieranie pojedynczego wpisu (tylko do odczytu)"
#: engine/blog/models.py:13 #: engine/blog/models.py:22
msgid "" msgid ""
"Represents a blog post model. The Post class defines the structure and " "Represents a blog post model. The Post class defines the structure and "
"behavior of a blog post. It includes attributes for author, title, content, " "behavior of a blog post. It includes attributes for author, title, content, "
"optional file attachment, slug, and associated tags. The class enforces " "optional file attachment, slug, and associated tags. The class enforces "
"constraints such as requiring either content or a file attachment but not " "constraints such as requiring either content or a file attachment but not "
"both simultaneously. It also supports automatic slug generation based on the " "both simultaneously. It also supports automatic slug generation based on the"
"title." " title."
msgstr "" msgstr ""
"Reprezentuje model wpisu na blogu. Klasa Post definiuje strukturę i " "Reprezentuje model wpisu na blogu. Klasa Post definiuje strukturę i "
"zachowanie wpisu na blogu. Zawiera atrybuty autora, tytułu, treści, " "zachowanie wpisu na blogu. Zawiera atrybuty autora, tytułu, treści, "
@ -41,64 +45,72 @@ msgstr ""
"nie obu jednocześnie. Obsługuje również automatyczne generowanie slug na " "nie obu jednocześnie. Obsługuje również automatyczne generowanie slug na "
"podstawie tytułu." "podstawie tytułu."
#: engine/blog/models.py:24 #: engine/blog/models.py:43
msgid "post title" msgid "post title"
msgstr "Tytuł postu" msgstr "Tytuł postu"
#: engine/blog/models.py:24 #: engine/blog/models.py:44
msgid "title" msgid "title"
msgstr "Tytuł" msgstr "Tytuł"
#: engine/blog/models.py:69 #: engine/blog/models.py:47
msgid "content"
msgstr "treść"
#: engine/blog/models.py:48
msgid "post content"
msgstr "treść postu"
#: engine/blog/models.py:68
msgid "is static page" msgid "is static page"
msgstr "jest stroną statyczną" msgstr "jest stroną statyczną"
#: engine/blog/models.py:70 #: engine/blog/models.py:70
msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?" msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?"
msgstr "" msgstr ""
"Czy jest to post dla strony ze statycznym adresem URL (np. `/help/delivery`)?" "Czy jest to post dla strony ze statycznym adresem URL (np. "
"`/help/delivery`)?"
#: engine/blog/models.py:77 #: engine/blog/models.py:82
msgid "post" msgid "post"
msgstr "Post" msgstr "Post"
#: engine/blog/models.py:78 #: engine/blog/models.py:83
msgid "posts" msgid "posts"
msgstr "Posty" msgstr "Posty"
#: engine/blog/models.py:82 #: engine/blog/models.py:88
msgid "markdown files are not supported yet - use markdown content instead" msgid "file uploads are not supported yet - use content instead"
msgstr "" msgstr ""
"Pliki Markdown nie są obsługiwane - zamiast tego użyj zawartości Markdown!" "Pliki Markdown nie są obsługiwane - zamiast tego użyj zawartości Markdown!"
#: engine/blog/models.py:84 #: engine/blog/models.py:92
msgid "" msgid "a file or content must be provided - mutually exclusive"
"a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"należy dostarczyć plik markdown lub zawartość markdown - wzajemnie się " "należy dostarczyć plik markdown lub zawartość markdown - wzajemnie się "
"wykluczające" "wykluczające"
#: engine/blog/models.py:116 #: engine/blog/models.py:125
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "wewnętrzny identyfikator tagu posta" msgstr "wewnętrzny identyfikator tagu posta"
#: engine/blog/models.py:117 #: engine/blog/models.py:126
msgid "tag name" msgid "tag name"
msgstr "Nazwa tagu" msgstr "Nazwa tagu"
#: engine/blog/models.py:121 #: engine/blog/models.py:130
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "Przyjazna dla użytkownika nazwa tagu posta" msgstr "Przyjazna dla użytkownika nazwa tagu posta"
#: engine/blog/models.py:122 #: engine/blog/models.py:131
msgid "tag display name" msgid "tag display name"
msgstr "Wyświetlana nazwa znacznika" msgstr "Wyświetlana nazwa znacznika"
#: engine/blog/models.py:130 #: engine/blog/models.py:139
msgid "post tag" msgid "post tag"
msgstr "Tag posta" msgstr "Tag posta"
#: engine/blog/models.py:131 #: engine/blog/models.py:140
msgid "post tags" msgid "post tags"
msgstr "Tagi postów" msgstr "Tagi postów"
@ -112,8 +124,8 @@ msgid ""
"permission configuration." "permission configuration."
msgstr "" msgstr ""
"Enkapsuluje operacje zarządzania i pobierania encji Post w zestawie widoków " "Enkapsuluje operacje zarządzania i pobierania encji Post w zestawie widoków "
"modelu tylko do odczytu. Klasa ta jest dostosowana do obsługi obiektów Post, " "modelu tylko do odczytu. Klasa ta jest dostosowana do obsługi obiektów Post,"
"które są aktywne i pozwala na filtrowanie w oparciu o zdefiniowane filtry. " " które są aktywne i pozwala na filtrowanie w oparciu o zdefiniowane filtry. "
"Integruje się z backendowym systemem filtrowania Django i zapewnia zgodność " "Integruje się z backendowym systemem filtrowania Django i zapewnia zgodność "
"operacji ze zdefiniowanymi uprawnieniami. Zestaw widoków zawiera również " "operacji ze zdefiniowanymi uprawnieniami. Zestaw widoków zawiera również "
"dodatkową konfigurację uprawnień \"retrieve\"." "dodatkową konfigurację uprawnień \"retrieve\"."

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2025.4\n" "Project-Id-Version: SCHON 2026.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-10 21:44+0300\n" "POT-Creation-Date: 2026-03-05 16:16+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
@ -13,6 +13,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: engine/blog/admin.py:49
msgid "picture preview"
msgstr "Pré-visualização da imagem"
#: engine/blog/apps.py:8 #: engine/blog/apps.py:8
msgid "blog" msgid "blog"
msgstr "Blog" msgstr "Blog"
@ -25,80 +29,88 @@ msgstr "Listar todas as postagens (somente leitura)"
msgid "retrieve a single post (read-only)" msgid "retrieve a single post (read-only)"
msgstr "Recuperar um único post (somente leitura)" msgstr "Recuperar um único post (somente leitura)"
#: engine/blog/models.py:13 #: engine/blog/models.py:22
msgid "" msgid ""
"Represents a blog post model. The Post class defines the structure and " "Represents a blog post model. The Post class defines the structure and "
"behavior of a blog post. It includes attributes for author, title, content, " "behavior of a blog post. It includes attributes for author, title, content, "
"optional file attachment, slug, and associated tags. The class enforces " "optional file attachment, slug, and associated tags. The class enforces "
"constraints such as requiring either content or a file attachment but not " "constraints such as requiring either content or a file attachment but not "
"both simultaneously. It also supports automatic slug generation based on the " "both simultaneously. It also supports automatic slug generation based on the"
"title." " title."
msgstr "" msgstr ""
"Representa um modelo de post de blog. A classe Post define a estrutura e o " "Representa um modelo de post de blog. A classe Post define a estrutura e o "
"comportamento de um post de blog. Ela inclui atributos para autor, título, " "comportamento de um post de blog. Ela inclui atributos para autor, título, "
"conteúdo, anexo de arquivo opcional, slug e tags associadas. A classe impõe " "conteúdo, anexo de arquivo opcional, slug e tags associadas. A classe impõe "
"restrições, como exigir conteúdo ou um anexo de arquivo, mas não ambos " "restrições, como exigir conteúdo ou um anexo de arquivo, mas não ambos "
"simultaneamente. Ela também oferece suporte à geração automática de slug com " "simultaneamente. Ela também oferece suporte à geração automática de slug com"
"base no título." " base no título."
#: engine/blog/models.py:24 #: engine/blog/models.py:43
msgid "post title" msgid "post title"
msgstr "Título da postagem" msgstr "Título da postagem"
#: engine/blog/models.py:24 #: engine/blog/models.py:44
msgid "title" msgid "title"
msgstr "Título" msgstr "Título"
#: engine/blog/models.py:69 #: engine/blog/models.py:47
msgid "content"
msgstr "conteúdo"
#: engine/blog/models.py:48
msgid "post content"
msgstr "conteúdo da publicação"
#: engine/blog/models.py:68
msgid "is static page" msgid "is static page"
msgstr "é uma página estática" msgstr "é uma página estática"
#: engine/blog/models.py:70 #: engine/blog/models.py:70
msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?" msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?"
msgstr "" msgstr ""
"Essa é uma postagem para uma página com URL estático (por exemplo, `/help/" "Essa é uma postagem para uma página com URL estático (por exemplo, "
"delivery`)?" "`/help/delivery`)?"
#: engine/blog/models.py:77 #: engine/blog/models.py:82
msgid "post" msgid "post"
msgstr "Postar" msgstr "Postar"
#: engine/blog/models.py:78 #: engine/blog/models.py:83
msgid "posts" msgid "posts"
msgstr "Publicações" msgstr "Publicações"
#: engine/blog/models.py:82 #: engine/blog/models.py:88
msgid "markdown files are not supported yet - use markdown content instead" msgid "file uploads are not supported yet - use content instead"
msgstr "" msgstr ""
"Os arquivos markdown não são suportados - use conteúdo markdown em vez disso!" "Os arquivos markdown não são suportados - use conteúdo markdown em vez "
"disso!"
#: engine/blog/models.py:84 #: engine/blog/models.py:92
msgid "" msgid "a file or content must be provided - mutually exclusive"
"a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"um arquivo ou conteúdo de markdown deve ser fornecido - mutuamente exclusivo" "um arquivo ou conteúdo de markdown deve ser fornecido - mutuamente exclusivo"
#: engine/blog/models.py:116 #: engine/blog/models.py:125
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "identificador de tag interno para a tag de postagem" msgstr "identificador de tag interno para a tag de postagem"
#: engine/blog/models.py:117 #: engine/blog/models.py:126
msgid "tag name" msgid "tag name"
msgstr "Nome da etiqueta" msgstr "Nome da etiqueta"
#: engine/blog/models.py:121 #: engine/blog/models.py:130
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "Nome de fácil utilização para a tag de postagem" msgstr "Nome de fácil utilização para a tag de postagem"
#: engine/blog/models.py:122 #: engine/blog/models.py:131
msgid "tag display name" msgid "tag display name"
msgstr "Nome de exibição da tag" msgstr "Nome de exibição da tag"
#: engine/blog/models.py:130 #: engine/blog/models.py:139
msgid "post tag" msgid "post tag"
msgstr "Etiqueta de postagem" msgstr "Etiqueta de postagem"
#: engine/blog/models.py:131 #: engine/blog/models.py:140
msgid "post tags" msgid "post tags"
msgstr "Tags de postagem" msgstr "Tags de postagem"

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2025.4\n" "Project-Id-Version: SCHON 2026.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-10 21:44+0300\n" "POT-Creation-Date: 2026-03-05 16:16+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
@ -13,6 +13,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: engine/blog/admin.py:49
msgid "picture preview"
msgstr "Previzualizare imagine"
#: engine/blog/apps.py:8 #: engine/blog/apps.py:8
msgid "blog" msgid "blog"
msgstr "Blog" msgstr "Blog"
@ -25,14 +29,14 @@ msgstr "Listează toate postările (doar pentru citire)"
msgid "retrieve a single post (read-only)" msgid "retrieve a single post (read-only)"
msgstr "Recuperează o singură postare (read-only)" msgstr "Recuperează o singură postare (read-only)"
#: engine/blog/models.py:13 #: engine/blog/models.py:22
msgid "" msgid ""
"Represents a blog post model. The Post class defines the structure and " "Represents a blog post model. The Post class defines the structure and "
"behavior of a blog post. It includes attributes for author, title, content, " "behavior of a blog post. It includes attributes for author, title, content, "
"optional file attachment, slug, and associated tags. The class enforces " "optional file attachment, slug, and associated tags. The class enforces "
"constraints such as requiring either content or a file attachment but not " "constraints such as requiring either content or a file attachment but not "
"both simultaneously. It also supports automatic slug generation based on the " "both simultaneously. It also supports automatic slug generation based on the"
"title." " title."
msgstr "" msgstr ""
"Reprezintă un model de postare pe blog. Clasa Post definește structura și " "Reprezintă un model de postare pe blog. Clasa Post definește structura și "
"comportamentul unei postări pe blog. Aceasta include atribute pentru autor, " "comportamentul unei postări pe blog. Aceasta include atribute pentru autor, "
@ -41,65 +45,73 @@ msgstr ""
"fișier atașat, dar nu ambele simultan. De asemenea, acceptă generarea " "fișier atașat, dar nu ambele simultan. De asemenea, acceptă generarea "
"automată a slug-ului pe baza titlului." "automată a slug-ului pe baza titlului."
#: engine/blog/models.py:24 #: engine/blog/models.py:43
msgid "post title" msgid "post title"
msgstr "Titlul postului" msgstr "Titlul postului"
#: engine/blog/models.py:24 #: engine/blog/models.py:44
msgid "title" msgid "title"
msgstr "Titlul" msgstr "Titlul"
#: engine/blog/models.py:69 #: engine/blog/models.py:47
msgid "content"
msgstr "conținut"
#: engine/blog/models.py:48
msgid "post content"
msgstr "conținutul postării"
#: engine/blog/models.py:68
msgid "is static page" msgid "is static page"
msgstr "este o pagină statică" msgstr "este o pagină statică"
#: engine/blog/models.py:70 #: engine/blog/models.py:70
msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?" msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?"
msgstr "" msgstr ""
"este aceasta o postare pentru o pagină cu URL static (de exemplu `/help/" "este aceasta o postare pentru o pagină cu URL static (de exemplu "
"delivery`)?" "`/help/delivery`)?"
#: engine/blog/models.py:77 #: engine/blog/models.py:82
msgid "post" msgid "post"
msgstr "Post" msgstr "Post"
#: engine/blog/models.py:78 #: engine/blog/models.py:83
msgid "posts" msgid "posts"
msgstr "Mesaje" msgstr "Mesaje"
#: engine/blog/models.py:82 #: engine/blog/models.py:88
msgid "markdown files are not supported yet - use markdown content instead" msgid "file uploads are not supported yet - use content instead"
msgstr "" msgstr ""
"Fișierele Markdown nu sunt acceptate - utilizați în schimb conținut Markdown!" "Fișierele Markdown nu sunt acceptate - utilizați în schimb conținut "
"Markdown!"
#: engine/blog/models.py:84 #: engine/blog/models.py:92
msgid "" msgid "a file or content must be provided - mutually exclusive"
"a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"trebuie furnizat un fișier markdown sau conținut markdown - se exclud " "trebuie furnizat un fișier markdown sau conținut markdown - se exclud "
"reciproc" "reciproc"
#: engine/blog/models.py:116 #: engine/blog/models.py:125
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "identificator intern de etichetă pentru eticheta postului" msgstr "identificator intern de etichetă pentru eticheta postului"
#: engine/blog/models.py:117 #: engine/blog/models.py:126
msgid "tag name" msgid "tag name"
msgstr "Nume etichetă" msgstr "Nume etichetă"
#: engine/blog/models.py:121 #: engine/blog/models.py:130
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "Nume ușor de utilizat pentru eticheta postului" msgstr "Nume ușor de utilizat pentru eticheta postului"
#: engine/blog/models.py:122 #: engine/blog/models.py:131
msgid "tag display name" msgid "tag display name"
msgstr "Nume afișare etichetă" msgstr "Nume afișare etichetă"
#: engine/blog/models.py:130 #: engine/blog/models.py:139
msgid "post tag" msgid "post tag"
msgstr "Etichetă post" msgstr "Etichetă post"
#: engine/blog/models.py:131 #: engine/blog/models.py:140
msgid "post tags" msgid "post tags"
msgstr "Etichete poștale" msgstr "Etichete poștale"
@ -112,8 +124,8 @@ msgid ""
"defined permissions. The view set also includes an additional 'retrieve' " "defined permissions. The view set also includes an additional 'retrieve' "
"permission configuration." "permission configuration."
msgstr "" msgstr ""
"Încapsulează operațiunile de gestionare și extragere a entităților Post într-" "Încapsulează operațiunile de gestionare și extragere a entităților Post "
"un set de vizualizări de model numai pentru citire. Această clasă este " "într-un set de vizualizări de model numai pentru citire. Această clasă este "
"adaptată pentru a gestiona obiectele Post care sunt active și permite " "adaptată pentru a gestiona obiectele Post care sunt active și permite "
"filtrarea pe baza filtrelor definite. Se integrează cu sistemul de filtrare " "filtrarea pe baza filtrelor definite. Se integrează cu sistemul de filtrare "
"din backend al Django și asigură alinierea operațiunilor cu permisiunile " "din backend al Django și asigură alinierea operațiunilor cu permisiunile "

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2025.4\n" "Project-Id-Version: SCHON 2026.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-10 21:44+0300\n" "POT-Creation-Date: 2026-03-05 16:16+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
@ -13,6 +13,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: engine/blog/admin.py:49
msgid "picture preview"
msgstr "Предварительный просмотр изображения"
#: engine/blog/apps.py:8 #: engine/blog/apps.py:8
msgid "blog" msgid "blog"
msgstr "Блог" msgstr "Блог"
@ -25,14 +29,14 @@ msgstr "Список всех сообщений (только для чтени
msgid "retrieve a single post (read-only)" msgid "retrieve a single post (read-only)"
msgstr "Получение одного сообщения (только для чтения)" msgstr "Получение одного сообщения (только для чтения)"
#: engine/blog/models.py:13 #: engine/blog/models.py:22
msgid "" msgid ""
"Represents a blog post model. The Post class defines the structure and " "Represents a blog post model. The Post class defines the structure and "
"behavior of a blog post. It includes attributes for author, title, content, " "behavior of a blog post. It includes attributes for author, title, content, "
"optional file attachment, slug, and associated tags. The class enforces " "optional file attachment, slug, and associated tags. The class enforces "
"constraints such as requiring either content or a file attachment but not " "constraints such as requiring either content or a file attachment but not "
"both simultaneously. It also supports automatic slug generation based on the " "both simultaneously. It also supports automatic slug generation based on the"
"title." " title."
msgstr "" msgstr ""
"Представляет модель записи в блоге. Класс Post определяет структуру и " "Представляет модель записи в блоге. Класс Post определяет структуру и "
"поведение записи в блоге. Он включает атрибуты автора, заголовка, " "поведение записи в блоге. Он включает атрибуты автора, заголовка, "
@ -41,15 +45,23 @@ msgstr ""
"вложение файла, но не то и другое одновременно. Он также поддерживает " "вложение файла, но не то и другое одновременно. Он также поддерживает "
"автоматическую генерацию slug на основе заголовка." "автоматическую генерацию slug на основе заголовка."
#: engine/blog/models.py:24 #: engine/blog/models.py:43
msgid "post title" msgid "post title"
msgstr "Заголовок сообщения" msgstr "Заголовок сообщения"
#: engine/blog/models.py:24 #: engine/blog/models.py:44
msgid "title" msgid "title"
msgstr "Название" msgstr "Название"
#: engine/blog/models.py:69 #: engine/blog/models.py:47
msgid "content"
msgstr "содержание"
#: engine/blog/models.py:48
msgid "post content"
msgstr "содержание публикации"
#: engine/blog/models.py:68
msgid "is static page" msgid "is static page"
msgstr "это статическая страница" msgstr "это статическая страница"
@ -58,48 +70,47 @@ msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?"
msgstr "" msgstr ""
"Это сообщение для страницы со статическим URL (например, `/help/delivery`)?" "Это сообщение для страницы со статическим URL (например, `/help/delivery`)?"
#: engine/blog/models.py:77 #: engine/blog/models.py:82
msgid "post" msgid "post"
msgstr "Пост" msgstr "Пост"
#: engine/blog/models.py:78 #: engine/blog/models.py:83
msgid "posts" msgid "posts"
msgstr "Посты" msgstr "Посты"
#: engine/blog/models.py:82 #: engine/blog/models.py:88
msgid "markdown files are not supported yet - use markdown content instead" msgid "file uploads are not supported yet - use content instead"
msgstr "" msgstr ""
"Файлы в формате Markdown не поддерживаются - используйте вместо них " "Файлы в формате Markdown не поддерживаются - используйте вместо них "
"содержимое в формате Markdown!" "содержимое в формате Markdown!"
#: engine/blog/models.py:84 #: engine/blog/models.py:92
msgid "" msgid "a file or content must be provided - mutually exclusive"
"a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"необходимо предоставить файл разметки или содержимое разметки - " "необходимо предоставить файл разметки или содержимое разметки - "
"взаимоисключающие варианты" "взаимоисключающие варианты"
#: engine/blog/models.py:116 #: engine/blog/models.py:125
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "внутренний идентификатор тега для тега post" msgstr "внутренний идентификатор тега для тега post"
#: engine/blog/models.py:117 #: engine/blog/models.py:126
msgid "tag name" msgid "tag name"
msgstr "Название тега" msgstr "Название тега"
#: engine/blog/models.py:121 #: engine/blog/models.py:130
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "Удобное для пользователя название тега поста" msgstr "Удобное для пользователя название тега поста"
#: engine/blog/models.py:122 #: engine/blog/models.py:131
msgid "tag display name" msgid "tag display name"
msgstr "Отображаемое имя тега" msgstr "Отображаемое имя тега"
#: engine/blog/models.py:130 #: engine/blog/models.py:139
msgid "post tag" msgid "post tag"
msgstr "Тэг поста" msgstr "Тэг поста"
#: engine/blog/models.py:131 #: engine/blog/models.py:140
msgid "post tags" msgid "post tags"
msgstr "Тэги постов" msgstr "Тэги постов"
@ -114,8 +125,8 @@ msgid ""
msgstr "" msgstr ""
"Инкапсулирует операции по управлению и получению объектов Post в наборе " "Инкапсулирует операции по управлению и получению объектов Post в наборе "
"представлений модели, доступном только для чтения. Этот класс предназначен " "представлений модели, доступном только для чтения. Этот класс предназначен "
"для работы с активными объектами Post и позволяет осуществлять фильтрацию на " "для работы с активными объектами Post и позволяет осуществлять фильтрацию на"
"основе заданных фильтров. Он интегрируется с системой фильтрации бэкенда " " основе заданных фильтров. Он интегрируется с системой фильтрации бэкенда "
"Django и обеспечивает соответствие операций заданным разрешениям. Набор " "Django и обеспечивает соответствие операций заданным разрешениям. Набор "
"представлений также включает дополнительную конфигурацию разрешения " "представлений также включает дополнительную конфигурацию разрешения "
"'retrieve'." "'retrieve'."

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2025.4\n" "Project-Id-Version: SCHON 2026.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-10 21:44+0300\n" "POT-Creation-Date: 2026-03-05 16:16+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
@ -13,6 +13,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: engine/blog/admin.py:49
msgid "picture preview"
msgstr "Bildförhandsvisning"
#: engine/blog/apps.py:8 #: engine/blog/apps.py:8
msgid "blog" msgid "blog"
msgstr "Blogg" msgstr "Blogg"
@ -25,14 +29,14 @@ msgstr "Lista alla inlägg (skrivskyddad)"
msgid "retrieve a single post (read-only)" msgid "retrieve a single post (read-only)"
msgstr "Hämta ett enskilt inlägg (skrivskyddat)" msgstr "Hämta ett enskilt inlägg (skrivskyddat)"
#: engine/blog/models.py:13 #: engine/blog/models.py:22
msgid "" msgid ""
"Represents a blog post model. The Post class defines the structure and " "Represents a blog post model. The Post class defines the structure and "
"behavior of a blog post. It includes attributes for author, title, content, " "behavior of a blog post. It includes attributes for author, title, content, "
"optional file attachment, slug, and associated tags. The class enforces " "optional file attachment, slug, and associated tags. The class enforces "
"constraints such as requiring either content or a file attachment but not " "constraints such as requiring either content or a file attachment but not "
"both simultaneously. It also supports automatic slug generation based on the " "both simultaneously. It also supports automatic slug generation based on the"
"title." " title."
msgstr "" msgstr ""
"Representerar en modell för blogginlägg. Klassen Post definierar strukturen " "Representerar en modell för blogginlägg. Klassen Post definierar strukturen "
"och beteendet för ett blogginlägg. Den innehåller attribut för författare, " "och beteendet för ett blogginlägg. Den innehåller attribut för författare, "
@ -41,15 +45,23 @@ msgstr ""
"filbilaga krävs, men inte båda samtidigt. Den stöder också automatisk " "filbilaga krävs, men inte båda samtidigt. Den stöder också automatisk "
"sluggenerering baserat på titeln." "sluggenerering baserat på titeln."
#: engine/blog/models.py:24 #: engine/blog/models.py:43
msgid "post title" msgid "post title"
msgstr "Inläggets titel" msgstr "Inläggets titel"
#: engine/blog/models.py:24 #: engine/blog/models.py:44
msgid "title" msgid "title"
msgstr "Titel" msgstr "Titel"
#: engine/blog/models.py:69 #: engine/blog/models.py:47
msgid "content"
msgstr "innehåll"
#: engine/blog/models.py:48
msgid "post content"
msgstr "inläggsinnehåll"
#: engine/blog/models.py:68
msgid "is static page" msgid "is static page"
msgstr "är statisk sida" msgstr "är statisk sida"
@ -58,46 +70,45 @@ msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?"
msgstr "" msgstr ""
"är detta ett inlägg för en sida med statisk URL (t.ex. `/help/delivery`)?" "är detta ett inlägg för en sida med statisk URL (t.ex. `/help/delivery`)?"
#: engine/blog/models.py:77 #: engine/blog/models.py:82
msgid "post" msgid "post"
msgstr "Post" msgstr "Post"
#: engine/blog/models.py:78 #: engine/blog/models.py:83
msgid "posts" msgid "posts"
msgstr "Inlägg" msgstr "Inlägg"
#: engine/blog/models.py:82 #: engine/blog/models.py:88
msgid "markdown files are not supported yet - use markdown content instead" msgid "file uploads are not supported yet - use content instead"
msgstr "Markdown-filer stöds inte - använd markdown-innehåll istället!" msgstr "filuppladdningar stöds ännu inte använd innehåll istället"
#: engine/blog/models.py:84 #: engine/blog/models.py:92
msgid "" msgid "a file or content must be provided - mutually exclusive"
"a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"en markdown-fil eller markdown-innehåll måste tillhandahållas - ömsesidigt " "en markdown-fil eller markdown-innehåll måste tillhandahållas - ömsesidigt "
"uteslutande" "uteslutande"
#: engine/blog/models.py:116 #: engine/blog/models.py:125
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "intern taggidentifierare för inläggstaggen" msgstr "intern taggidentifierare för inläggstaggen"
#: engine/blog/models.py:117 #: engine/blog/models.py:126
msgid "tag name" msgid "tag name"
msgstr "Tagg namn" msgstr "Tagg namn"
#: engine/blog/models.py:121 #: engine/blog/models.py:130
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "Användarvänligt namn för inläggstaggen" msgstr "Användarvänligt namn för inläggstaggen"
#: engine/blog/models.py:122 #: engine/blog/models.py:131
msgid "tag display name" msgid "tag display name"
msgstr "Taggens visningsnamn" msgstr "Taggens visningsnamn"
#: engine/blog/models.py:130 #: engine/blog/models.py:139
msgid "post tag" msgid "post tag"
msgstr "Post tagg" msgstr "Post tagg"
#: engine/blog/models.py:131 #: engine/blog/models.py:140
msgid "post tags" msgid "post tags"
msgstr "Taggar för inlägg" msgstr "Taggar för inlägg"
@ -115,5 +126,5 @@ msgstr ""
"objekt som är aktiva och tillåter filtrering baserat på definierade filter. " "objekt som är aktiva och tillåter filtrering baserat på definierade filter. "
"Den integreras med Djangos backend-filtreringssystem och säkerställer att " "Den integreras med Djangos backend-filtreringssystem och säkerställer att "
"operationerna överensstämmer med de definierade behörigheterna. " "operationerna överensstämmer med de definierade behörigheterna. "
"Vyuppsättningen innehåller också en ytterligare behörighetskonfiguration för " "Vyuppsättningen innehåller också en ytterligare behörighetskonfiguration för"
"\"hämta\"." " \"hämta\"."

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2025.4\n" "Project-Id-Version: SCHON 2026.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-10 21:44+0300\n" "POT-Creation-Date: 2026-03-05 16:16+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
@ -13,6 +13,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: engine/blog/admin.py:49
msgid "picture preview"
msgstr "ภาพตัวอย่าง"
#: engine/blog/apps.py:8 #: engine/blog/apps.py:8
msgid "blog" msgid "blog"
msgstr "บล็อก" msgstr "บล็อก"
@ -25,29 +29,40 @@ msgstr "แสดงรายการโพสต์ทั้งหมด (อ
msgid "retrieve a single post (read-only)" msgid "retrieve a single post (read-only)"
msgstr "ดึงโพสต์เดียว (อ่านอย่างเดียว)" msgstr "ดึงโพสต์เดียว (อ่านอย่างเดียว)"
#: engine/blog/models.py:13 #: engine/blog/models.py:22
msgid "" msgid ""
"Represents a blog post model. The Post class defines the structure and " "Represents a blog post model. The Post class defines the structure and "
"behavior of a blog post. It includes attributes for author, title, content, " "behavior of a blog post. It includes attributes for author, title, content, "
"optional file attachment, slug, and associated tags. The class enforces " "optional file attachment, slug, and associated tags. The class enforces "
"constraints such as requiring either content or a file attachment but not " "constraints such as requiring either content or a file attachment but not "
"both simultaneously. It also supports automatic slug generation based on the " "both simultaneously. It also supports automatic slug generation based on the"
"title." " title."
msgstr "" msgstr ""
"แทนแบบจำลองของโพสต์ในบล็อก คลาส Post กำหนดโครงสร้างและพฤติกรรมของโพสต์ในบล็อก " "แทนแบบจำลองของโพสต์ในบล็อก คลาส Post "
"ประกอบด้วยแอตทริบิวต์สำหรับผู้เขียน, ชื่อเรื่อง, เนื้อหา, ไฟล์แนบ (ไม่บังคับ), slug " "กำหนดโครงสร้างและพฤติกรรมของโพสต์ในบล็อก "
"และแท็กที่เกี่ยวข้อง คลาสนี้บังคับใช้ข้อจำกัด เช่น ต้องมีเนื้อหาหรือไฟล์แนบอย่างใดอย่างหนึ่ง " "ประกอบด้วยแอตทริบิวต์สำหรับผู้เขียน, ชื่อเรื่อง, เนื้อหา, ไฟล์แนบ "
"แต่ไม่สามารถมีทั้งสองอย่างพร้อมกันได้ นอกจากนี้ยังรองรับการสร้าง slug โดยอัตโนมัติจากชื่อเรื่อง" "(ไม่บังคับ), slug และแท็กที่เกี่ยวข้อง คลาสนี้บังคับใช้ข้อจำกัด เช่น "
"ต้องมีเนื้อหาหรือไฟล์แนบอย่างใดอย่างหนึ่ง "
"แต่ไม่สามารถมีทั้งสองอย่างพร้อมกันได้ นอกจากนี้ยังรองรับการสร้าง slug "
"โดยอัตโนมัติจากชื่อเรื่อง"
#: engine/blog/models.py:24 #: engine/blog/models.py:43
msgid "post title" msgid "post title"
msgstr "ชื่อโพสต์" msgstr "ชื่อโพสต์"
#: engine/blog/models.py:24 #: engine/blog/models.py:44
msgid "title" msgid "title"
msgstr "ชื่อเรื่อง" msgstr "ชื่อเรื่อง"
#: engine/blog/models.py:69 #: engine/blog/models.py:47
msgid "content"
msgstr "เนื้อหา"
#: engine/blog/models.py:48
msgid "post content"
msgstr "โพสต์เนื้อหา"
#: engine/blog/models.py:68
msgid "is static page" msgid "is static page"
msgstr "เป็นหน้าคงที่" msgstr "เป็นหน้าคงที่"
@ -55,44 +70,45 @@ msgstr "เป็นหน้าคงที่"
msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?" msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?"
msgstr "นี่คือโพสต์สำหรับหน้าที่มี URL แบบคงที่ (เช่น `/help/delivery`)?" msgstr "นี่คือโพสต์สำหรับหน้าที่มี URL แบบคงที่ (เช่น `/help/delivery`)?"
#: engine/blog/models.py:77 #: engine/blog/models.py:82
msgid "post" msgid "post"
msgstr "โพสต์" msgstr "โพสต์"
#: engine/blog/models.py:78 #: engine/blog/models.py:83
msgid "posts" msgid "posts"
msgstr "โพสต์" msgstr "โพสต์"
#: engine/blog/models.py:82 #: engine/blog/models.py:88
msgid "markdown files are not supported yet - use markdown content instead" msgid "file uploads are not supported yet - use content instead"
msgstr "ไฟล์มาร์กดาวน์ยังไม่รองรับในตอนนี้ - กรุณาใช้เนื้อหาแบบมาร์กดาวน์แทน!" msgstr "ยังไม่รองรับการอัปโหลดไฟล์ - กรุณาใช้เนื้อหาแทน"
#: engine/blog/models.py:84 #: engine/blog/models.py:92
msgid "" msgid "a file or content must be provided - mutually exclusive"
"a markdown file or markdown content must be provided - mutually exclusive" msgstr ""
msgstr "ไฟล์มาร์กดาวน์หรือเนื้อหาแบบมาร์กดาวน์ต้องได้รับการจัดเตรียมไว้ - ไม่สามารถใช้ร่วมกันได้" "ไฟล์มาร์กดาวน์หรือเนื้อหาแบบมาร์กดาวน์ต้องได้รับการจัดเตรียมไว้ - "
"ไม่สามารถใช้ร่วมกันได้"
#: engine/blog/models.py:116 #: engine/blog/models.py:125
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "ตัวระบุแท็กภายในสำหรับแท็กโพสต์" msgstr "ตัวระบุแท็กภายในสำหรับแท็กโพสต์"
#: engine/blog/models.py:117 #: engine/blog/models.py:126
msgid "tag name" msgid "tag name"
msgstr "ชื่อวัน" msgstr "ชื่อวัน"
#: engine/blog/models.py:121 #: engine/blog/models.py:130
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "ชื่อที่ใช้งานได้ง่ายสำหรับแท็กโพสต์" msgstr "ชื่อที่ใช้งานได้ง่ายสำหรับแท็กโพสต์"
#: engine/blog/models.py:122 #: engine/blog/models.py:131
msgid "tag display name" msgid "tag display name"
msgstr "แสดงชื่อแท็ก" msgstr "แสดงชื่อแท็ก"
#: engine/blog/models.py:130 #: engine/blog/models.py:139
msgid "post tag" msgid "post tag"
msgstr "โพสต์แท็ก" msgstr "โพสต์แท็ก"
#: engine/blog/models.py:131 #: engine/blog/models.py:140
msgid "post tags" msgid "post tags"
msgstr "ป้ายกำกับโพสต์" msgstr "ป้ายกำกับโพสต์"
@ -105,8 +121,9 @@ msgid ""
"defined permissions. The view set also includes an additional 'retrieve' " "defined permissions. The view set also includes an additional 'retrieve' "
"permission configuration." "permission configuration."
msgstr "" msgstr ""
"รวบรวมการดำเนินการสำหรับการจัดการและดึงข้อมูลเอนทิตีโพสต์ในชุดมุมมองแบบอ่านอย่างเดียว " "รวบรวมการดำเนินการสำหรับการจัดการและดึงข้อมูลเอนทิตีโพสต์ในชุดมุมมองแบบอ่านอย่างเดียว"
"คลาสนี้ถูกออกแบบมาเพื่อจัดการกับวัตถุโพสต์ที่ใช้งานอยู่และอนุญาตให้มีการกรองตามตัวกรองที่กำหนดไว้ " " "
"มันผสานรวมกับระบบการกรองแบ็กเอนด์ของ Django " "คลาสนี้ถูกออกแบบมาเพื่อจัดการกับวัตถุโพสต์ที่ใช้งานอยู่และอนุญาตให้มีการกรองตามตัวกรองที่กำหนดไว้"
"และทำให้แน่ใจว่าการดำเนินการสอดคล้องกับสิทธิ์ที่กำหนดไว้ ชุดมุมมองยังรวมถึงการกำหนดค่าสิทธิ์ " " มันผสานรวมกับระบบการกรองแบ็กเอนด์ของ Django "
"'ดึงข้อมูล' เพิ่มเติมด้วย" "และทำให้แน่ใจว่าการดำเนินการสอดคล้องกับสิทธิ์ที่กำหนดไว้ "
"ชุดมุมมองยังรวมถึงการกำหนดค่าสิทธิ์ 'ดึงข้อมูล' เพิ่มเติมด้วย"

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2025.4\n" "Project-Id-Version: SCHON 2026.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-10 21:44+0300\n" "POT-Creation-Date: 2026-03-05 16:16+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
@ -13,6 +13,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: engine/blog/admin.py:49
msgid "picture preview"
msgstr "Resim önizlemesi"
#: engine/blog/apps.py:8 #: engine/blog/apps.py:8
msgid "blog" msgid "blog"
msgstr "Blog" msgstr "Blog"
@ -25,31 +29,39 @@ msgstr "Tüm gönderileri listele (salt okunur)"
msgid "retrieve a single post (read-only)" msgid "retrieve a single post (read-only)"
msgstr "Tek bir gönderiyi al (salt okunur)" msgstr "Tek bir gönderiyi al (salt okunur)"
#: engine/blog/models.py:13 #: engine/blog/models.py:22
msgid "" msgid ""
"Represents a blog post model. The Post class defines the structure and " "Represents a blog post model. The Post class defines the structure and "
"behavior of a blog post. It includes attributes for author, title, content, " "behavior of a blog post. It includes attributes for author, title, content, "
"optional file attachment, slug, and associated tags. The class enforces " "optional file attachment, slug, and associated tags. The class enforces "
"constraints such as requiring either content or a file attachment but not " "constraints such as requiring either content or a file attachment but not "
"both simultaneously. It also supports automatic slug generation based on the " "both simultaneously. It also supports automatic slug generation based on the"
"title." " title."
msgstr "" msgstr ""
"Bir blog yazısı modelini temsil eder. Post sınıfı, bir blog gönderisinin " "Bir blog yazısı modelini temsil eder. Post sınıfı, bir blog gönderisinin "
"yapısını ve davranışını tanımlar. Yazar, başlık, içerik, isteğe bağlı dosya " "yapısını ve davranışını tanımlar. Yazar, başlık, içerik, isteğe bağlı dosya "
"eki, slug ve ilişkili etiketler için öznitelikler içerir. Sınıf, içerik veya " "eki, slug ve ilişkili etiketler için öznitelikler içerir. Sınıf, içerik veya"
"dosya eki gerektirme ancak her ikisini aynı anda gerektirmeme gibi " " dosya eki gerektirme ancak her ikisini aynı anda gerektirmeme gibi "
"kısıtlamalar uygular. Ayrıca başlığa dayalı otomatik slug oluşturmayı da " "kısıtlamalar uygular. Ayrıca başlığa dayalı otomatik slug oluşturmayı da "
"destekler." "destekler."
#: engine/blog/models.py:24 #: engine/blog/models.py:43
msgid "post title" msgid "post title"
msgstr "Gönderinin başlığı" msgstr "Gönderinin başlığı"
#: engine/blog/models.py:24 #: engine/blog/models.py:44
msgid "title" msgid "title"
msgstr "Başlık" msgstr "Başlık"
#: engine/blog/models.py:69 #: engine/blog/models.py:47
msgid "content"
msgstr "içerik"
#: engine/blog/models.py:48
msgid "post content"
msgstr "yazı içeriği"
#: engine/blog/models.py:68
msgid "is static page" msgid "is static page"
msgstr "statik sayfadır" msgstr "statik sayfadır"
@ -59,47 +71,47 @@ msgstr ""
"Bu, statik URL'ye sahip bir sayfa (örneğin `/help/delivery`) için bir " "Bu, statik URL'ye sahip bir sayfa (örneğin `/help/delivery`) için bir "
"gönderi mi?" "gönderi mi?"
#: engine/blog/models.py:77 #: engine/blog/models.py:82
msgid "post" msgid "post"
msgstr "Posta" msgstr "Posta"
#: engine/blog/models.py:78 #: engine/blog/models.py:83
msgid "posts" msgid "posts"
msgstr "Mesajlar" msgstr "Mesajlar"
#: engine/blog/models.py:82 #: engine/blog/models.py:88
msgid "markdown files are not supported yet - use markdown content instead" msgid "file uploads are not supported yet - use content instead"
msgstr "" msgstr ""
"Markdown dosyaları desteklenmiyor yer - bunun yerine markdown içeriği " "Markdown dosyaları desteklenmiyor yer - bunun yerine markdown içeriği "
"kullanın!" "kullanın!"
#: engine/blog/models.py:84 #: engine/blog/models.py:92
msgid "" msgid "a file or content must be provided - mutually exclusive"
"a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"bir markdown dosyası veya markdown içeriği sağlanmalıdır - birbirini dışlayan" "bir markdown dosyası veya markdown içeriği sağlanmalıdır - birbirini "
"dışlayan"
#: engine/blog/models.py:116 #: engine/blog/models.py:125
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "gönderi etiketi için dahili etiket tanımlayıcısı" msgstr "gönderi etiketi için dahili etiket tanımlayıcısı"
#: engine/blog/models.py:117 #: engine/blog/models.py:126
msgid "tag name" msgid "tag name"
msgstr "Etiket adı" msgstr "Etiket adı"
#: engine/blog/models.py:121 #: engine/blog/models.py:130
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "Gönderi etiketi için kullanıcı dostu ad" msgstr "Gönderi etiketi için kullanıcı dostu ad"
#: engine/blog/models.py:122 #: engine/blog/models.py:131
msgid "tag display name" msgid "tag display name"
msgstr "Etiket görünen adı" msgstr "Etiket görünen adı"
#: engine/blog/models.py:130 #: engine/blog/models.py:139
msgid "post tag" msgid "post tag"
msgstr "Mesaj etiketi" msgstr "Mesaj etiketi"
#: engine/blog/models.py:131 #: engine/blog/models.py:140
msgid "post tags" msgid "post tags"
msgstr "Gönderi etiketleri" msgstr "Gönderi etiketleri"

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2025.4\n" "Project-Id-Version: SCHON 2026.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-10 21:44+0300\n" "POT-Creation-Date: 2026-03-05 16:16+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
@ -13,6 +13,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: engine/blog/admin.py:49
msgid "picture preview"
msgstr "Xem trước hình ảnh"
#: engine/blog/apps.py:8 #: engine/blog/apps.py:8
msgid "blog" msgid "blog"
msgstr "Blog" msgstr "Blog"
@ -25,14 +29,14 @@ msgstr "Danh sách tất cả các bài đăng (chỉ đọc)"
msgid "retrieve a single post (read-only)" msgid "retrieve a single post (read-only)"
msgstr "Lấy một bài đăng duy nhất (chỉ đọc)" msgstr "Lấy một bài đăng duy nhất (chỉ đọc)"
#: engine/blog/models.py:13 #: engine/blog/models.py:22
msgid "" msgid ""
"Represents a blog post model. The Post class defines the structure and " "Represents a blog post model. The Post class defines the structure and "
"behavior of a blog post. It includes attributes for author, title, content, " "behavior of a blog post. It includes attributes for author, title, content, "
"optional file attachment, slug, and associated tags. The class enforces " "optional file attachment, slug, and associated tags. The class enforces "
"constraints such as requiring either content or a file attachment but not " "constraints such as requiring either content or a file attachment but not "
"both simultaneously. It also supports automatic slug generation based on the " "both simultaneously. It also supports automatic slug generation based on the"
"title." " title."
msgstr "" msgstr ""
"Đại diện cho mô hình bài viết blog. Lớp Post định nghĩa cấu trúc và hành vi " "Đại diện cho mô hình bài viết blog. Lớp Post định nghĩa cấu trúc và hành vi "
"của một bài viết blog. Nó bao gồm các thuộc tính cho tác giả, tiêu đề, nội " "của một bài viết blog. Nó bao gồm các thuộc tính cho tác giả, tiêu đề, nội "
@ -40,15 +44,23 @@ msgstr ""
"ràng buộc như yêu cầu phải có nội dung hoặc tệp đính kèm nhưng không cả hai " "ràng buộc như yêu cầu phải có nội dung hoặc tệp đính kèm nhưng không cả hai "
"cùng lúc. Nó cũng hỗ trợ tạo slug tự động dựa trên tiêu đề." "cùng lúc. Nó cũng hỗ trợ tạo slug tự động dựa trên tiêu đề."
#: engine/blog/models.py:24 #: engine/blog/models.py:43
msgid "post title" msgid "post title"
msgstr "Tiêu đề bài đăng" msgstr "Tiêu đề bài đăng"
#: engine/blog/models.py:24 #: engine/blog/models.py:44
msgid "title" msgid "title"
msgstr "Tiêu đề" msgstr "Tiêu đề"
#: engine/blog/models.py:69 #: engine/blog/models.py:47
msgid "content"
msgstr "nội dung"
#: engine/blog/models.py:48
msgid "post content"
msgstr "Nội dung bài đăng"
#: engine/blog/models.py:68
msgid "is static page" msgid "is static page"
msgstr "là trang tĩnh" msgstr "là trang tĩnh"
@ -58,47 +70,46 @@ msgstr ""
"Đây có phải là bài đăng cho một trang có URL tĩnh (ví dụ: `/help/delivery`) " "Đây có phải là bài đăng cho một trang có URL tĩnh (ví dụ: `/help/delivery`) "
"không?" "không?"
#: engine/blog/models.py:77 #: engine/blog/models.py:82
msgid "post" msgid "post"
msgstr "Bài đăng" msgstr "Bài đăng"
#: engine/blog/models.py:78 #: engine/blog/models.py:83
msgid "posts" msgid "posts"
msgstr "Bài đăng" msgstr "Bài đăng"
#: engine/blog/models.py:82 #: engine/blog/models.py:88
msgid "markdown files are not supported yet - use markdown content instead" msgid "file uploads are not supported yet - use content instead"
msgstr "" msgstr ""
"Tệp Markdown hiện chưa được hỗ trợ - hãy sử dụng nội dung Markdown thay thế!" "Tệp Markdown hiện chưa được hỗ trợ - hãy sử dụng nội dung Markdown thay thế!"
#: engine/blog/models.py:84 #: engine/blog/models.py:92
msgid "" msgid "a file or content must be provided - mutually exclusive"
"a markdown file or markdown content must be provided - mutually exclusive"
msgstr "" msgstr ""
"Phải cung cấp tệp Markdown hoặc nội dung Markdown - hai tùy chọn này là " "Phải cung cấp tệp Markdown hoặc nội dung Markdown - hai tùy chọn này là "
"tương phản nhau." "tương phản nhau."
#: engine/blog/models.py:116 #: engine/blog/models.py:125
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "Mã định danh thẻ nội bộ cho thẻ bài viết" msgstr "Mã định danh thẻ nội bộ cho thẻ bài viết"
#: engine/blog/models.py:117 #: engine/blog/models.py:126
msgid "tag name" msgid "tag name"
msgstr "Tên ngày" msgstr "Tên ngày"
#: engine/blog/models.py:121 #: engine/blog/models.py:130
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "Tên thân thiện với người dùng cho thẻ bài viết" msgstr "Tên thân thiện với người dùng cho thẻ bài viết"
#: engine/blog/models.py:122 #: engine/blog/models.py:131
msgid "tag display name" msgid "tag display name"
msgstr "Hiển thị tên thẻ" msgstr "Hiển thị tên thẻ"
#: engine/blog/models.py:130 #: engine/blog/models.py:139
msgid "post tag" msgid "post tag"
msgstr "Thẻ bài viết" msgstr "Thẻ bài viết"
#: engine/blog/models.py:131 #: engine/blog/models.py:140
msgid "post tags" msgid "post tags"
msgstr "Thẻ bài viết" msgstr "Thẻ bài viết"
@ -113,6 +124,7 @@ msgid ""
msgstr "" msgstr ""
"Đóng gói các thao tác quản lý và truy xuất các thực thể Post trong một bộ " "Đóng gói các thao tác quản lý và truy xuất các thực thể Post trong một bộ "
"xem mô hình chỉ đọc. Lớp này được thiết kế để xử lý các đối tượng Post đang " "xem mô hình chỉ đọc. Lớp này được thiết kế để xử lý các đối tượng Post đang "
"hoạt động và cho phép lọc dựa trên các bộ lọc đã định nghĩa. Nó tích hợp với " "hoạt động và cho phép lọc dựa trên các bộ lọc đã định nghĩa. Nó tích hợp với"
"hệ thống lọc phía sau của Django và đảm bảo các thao tác tuân thủ các quyền " " hệ thống lọc phía sau của Django và đảm bảo các thao tác tuân thủ các quyền"
"hạn đã định nghĩa. Bộ xem cũng bao gồm một cấu hình quyền 'retrieve' bổ sung." " hạn đã định nghĩa. Bộ xem cũng bao gồm một cấu hình quyền 'retrieve' bổ "
"sung."

View file

@ -1,9 +1,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: EVIBES 2025.4\n" "Project-Id-Version: SCHON 2026.1\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-10 21:44+0300\n" "POT-Creation-Date: 2026-03-05 16:16+0300\n"
"PO-Revision-Date: 2025-06-16 08:59+0100\n" "PO-Revision-Date: 2025-06-16 08:59+0100\n"
"Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n" "Last-Translator: EGOR GORBUNOV <CONTACT@FUREUNOIR.COM>\n"
"Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n" "Language-Team: LANGUAGE <CONTACT@FUREUNOIR.COM>\n"
@ -13,6 +13,10 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: engine/blog/admin.py:49
msgid "picture preview"
msgstr "图片预览"
#: engine/blog/apps.py:8 #: engine/blog/apps.py:8
msgid "blog" msgid "blog"
msgstr "博客" msgstr "博客"
@ -25,28 +29,34 @@ msgstr "列出所有帖子(只读)"
msgid "retrieve a single post (read-only)" msgid "retrieve a single post (read-only)"
msgstr "检索单个帖子(只读)" msgstr "检索单个帖子(只读)"
#: engine/blog/models.py:13 #: engine/blog/models.py:22
msgid "" msgid ""
"Represents a blog post model. The Post class defines the structure and " "Represents a blog post model. The Post class defines the structure and "
"behavior of a blog post. It includes attributes for author, title, content, " "behavior of a blog post. It includes attributes for author, title, content, "
"optional file attachment, slug, and associated tags. The class enforces " "optional file attachment, slug, and associated tags. The class enforces "
"constraints such as requiring either content or a file attachment but not " "constraints such as requiring either content or a file attachment but not "
"both simultaneously. It also supports automatic slug generation based on the " "both simultaneously. It also supports automatic slug generation based on the"
"title." " title."
msgstr "" msgstr ""
"代表博文模型。帖子类定义了博文的结构和行为。它包括作者、标题、内容、可选文件" "代表博文模型。帖子类定义了博文的结构和行为。它包括作者、标题、内容、可选文件附件、标签和相关标记的属性。该类可强制执行一些限制条件,如要求提供内容或文件附件,但不能同时提供这两种内容。它还支持根据标题自动生成标签。"
"附件、标签和相关标记的属性。该类可强制执行一些限制条件,如要求提供内容或文件"
"附件,但不能同时提供这两种内容。它还支持根据标题自动生成标签。"
#: engine/blog/models.py:24 #: engine/blog/models.py:43
msgid "post title" msgid "post title"
msgstr "帖子标题" msgstr "帖子标题"
#: engine/blog/models.py:24 #: engine/blog/models.py:44
msgid "title" msgid "title"
msgstr "标题" msgstr "标题"
#: engine/blog/models.py:69 #: engine/blog/models.py:47
msgid "content"
msgstr "内容"
#: engine/blog/models.py:48
msgid "post content"
msgstr "发布内容"
#: engine/blog/models.py:68
msgid "is static page" msgid "is static page"
msgstr "是静态页面" msgstr "是静态页面"
@ -54,44 +64,43 @@ msgstr "是静态页面"
msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?" msgid "is this a post for a page with static URL (e.g. `/help/delivery`)?"
msgstr "这是静态 URL 页面(如 `/help/delivery`)的帖子吗?" msgstr "这是静态 URL 页面(如 `/help/delivery`)的帖子吗?"
#: engine/blog/models.py:77 #: engine/blog/models.py:82
msgid "post" msgid "post"
msgstr "职位" msgstr "职位"
#: engine/blog/models.py:78 #: engine/blog/models.py:83
msgid "posts" msgid "posts"
msgstr "职位" msgstr "职位"
#: engine/blog/models.py:82 #: engine/blog/models.py:88
msgid "markdown files are not supported yet - use markdown content instead" msgid "file uploads are not supported yet - use content instead"
msgstr "不支持 Markdown 文件,请使用 Markdown 内容!" msgstr "目前尚不支持文件上传——请改用内容上传功能"
#: engine/blog/models.py:84 #: engine/blog/models.py:92
msgid "" msgid "a file or content must be provided - mutually exclusive"
"a markdown file or markdown content must be provided - mutually exclusive" msgstr "必须提供一个Markdown文件或Markdown内容——两者互斥"
msgstr "必须提供标记符文件或标记符内容 - 相互排斥"
#: engine/blog/models.py:116 #: engine/blog/models.py:125
msgid "internal tag identifier for the post tag" msgid "internal tag identifier for the post tag"
msgstr "职位标签的内部标签标识符" msgstr "职位标签的内部标签标识符"
#: engine/blog/models.py:117 #: engine/blog/models.py:126
msgid "tag name" msgid "tag name"
msgstr "标签名称" msgstr "标签名称"
#: engine/blog/models.py:121 #: engine/blog/models.py:130
msgid "user-friendly name for the post tag" msgid "user-friendly name for the post tag"
msgstr "方便用户使用的帖子标签名称" msgstr "方便用户使用的帖子标签名称"
#: engine/blog/models.py:122 #: engine/blog/models.py:131
msgid "tag display name" msgid "tag display name"
msgstr "标签显示名称" msgstr "标签显示名称"
#: engine/blog/models.py:130 #: engine/blog/models.py:139
msgid "post tag" msgid "post tag"
msgstr "职位标签" msgstr "职位标签"
#: engine/blog/models.py:131 #: engine/blog/models.py:140
msgid "post tags" msgid "post tags"
msgstr "帖子标签" msgstr "帖子标签"
@ -104,6 +113,5 @@ msgid ""
"defined permissions. The view set also includes an additional 'retrieve' " "defined permissions. The view set also includes an additional 'retrieve' "
"permission configuration." "permission configuration."
msgstr "" msgstr ""
"该类封装了在只读模型视图集中管理和检索 \"帖子 \"实体的操作。该类专门用于处理" "该类封装了在只读模型视图集中管理和检索 \"帖子 \"实体的操作。该类专门用于处理活动的 \"帖子 \"对象,并允许根据定义的过滤器进行过滤。它与 "
"活动的 \"帖子 \"对象,并允许根据定义的过滤器进行过滤。它与 Django 的后台过滤" "Django 的后台过滤系统集成,确保操作与定义的权限一致。视图集还包括额外的 \"检索 \"权限配置。"
"系统集成,确保操作与定义的权限一致。视图集还包括额外的 \"检索 \"权限配置。"

View file

@ -0,0 +1,215 @@
# Generated by Django 5.2.9 on 2026-01-26 12:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("blog", "0007_post_is_static_page"),
]
operations = [
migrations.AlterField(
model_name="post",
name="content",
field=models.TextField(
blank=True, help_text="post content", null=True, verbose_name="content"
),
),
migrations.AlterField(
model_name="post",
name="content_ar_ar",
field=models.TextField(
blank=True, help_text="post content", null=True, verbose_name="content"
),
),
migrations.AlterField(
model_name="post",
name="content_cs_cz",
field=models.TextField(
blank=True, help_text="post content", null=True, verbose_name="content"
),
),
migrations.AlterField(
model_name="post",
name="content_da_dk",
field=models.TextField(
blank=True, help_text="post content", null=True, verbose_name="content"
),
),
migrations.AlterField(
model_name="post",
name="content_de_de",
field=models.TextField(
blank=True, help_text="post content", null=True, verbose_name="content"
),
),
migrations.AlterField(
model_name="post",
name="content_en_gb",
field=models.TextField(
blank=True, help_text="post content", null=True, verbose_name="content"
),
),
migrations.AlterField(
model_name="post",
name="content_en_us",
field=models.TextField(
blank=True, help_text="post content", null=True, verbose_name="content"
),
),
migrations.AlterField(
model_name="post",
name="content_es_es",
field=models.TextField(
blank=True, help_text="post content", null=True, verbose_name="content"
),
),
migrations.AlterField(
model_name="post",
name="content_fa_ir",
field=models.TextField(
blank=True, help_text="post content", null=True, verbose_name="content"
),
),
migrations.AlterField(
model_name="post",
name="content_fr_fr",
field=models.TextField(
blank=True, help_text="post content", null=True, verbose_name="content"
),
),
migrations.AlterField(
model_name="post",
name="content_he_il",
field=models.TextField(
blank=True, help_text="post content", null=True, verbose_name="content"
),
),
migrations.AlterField(
model_name="post",
name="content_hi_in",
field=models.TextField(
blank=True, help_text="post content", null=True, verbose_name="content"
),
),
migrations.AlterField(
model_name="post",
name="content_hr_hr",
field=models.TextField(
blank=True, help_text="post content", null=True, verbose_name="content"
),
),
migrations.AlterField(
model_name="post",
name="content_id_id",
field=models.TextField(
blank=True, help_text="post content", null=True, verbose_name="content"
),
),
migrations.AlterField(
model_name="post",
name="content_it_it",
field=models.TextField(
blank=True, help_text="post content", null=True, verbose_name="content"
),
),
migrations.AlterField(
model_name="post",
name="content_ja_jp",
field=models.TextField(
blank=True, help_text="post content", null=True, verbose_name="content"
),
),
migrations.AlterField(
model_name="post",
name="content_kk_kz",
field=models.TextField(
blank=True, help_text="post content", null=True, verbose_name="content"
),
),
migrations.AlterField(
model_name="post",
name="content_ko_kr",
field=models.TextField(
blank=True, help_text="post content", null=True, verbose_name="content"
),
),
migrations.AlterField(
model_name="post",
name="content_nl_nl",
field=models.TextField(
blank=True, help_text="post content", null=True, verbose_name="content"
),
),
migrations.AlterField(
model_name="post",
name="content_no_no",
field=models.TextField(
blank=True, help_text="post content", null=True, verbose_name="content"
),
),
migrations.AlterField(
model_name="post",
name="content_pl_pl",
field=models.TextField(
blank=True, help_text="post content", null=True, verbose_name="content"
),
),
migrations.AlterField(
model_name="post",
name="content_pt_br",
field=models.TextField(
blank=True, help_text="post content", null=True, verbose_name="content"
),
),
migrations.AlterField(
model_name="post",
name="content_ro_ro",
field=models.TextField(
blank=True, help_text="post content", null=True, verbose_name="content"
),
),
migrations.AlterField(
model_name="post",
name="content_ru_ru",
field=models.TextField(
blank=True, help_text="post content", null=True, verbose_name="content"
),
),
migrations.AlterField(
model_name="post",
name="content_sv_se",
field=models.TextField(
blank=True, help_text="post content", null=True, verbose_name="content"
),
),
migrations.AlterField(
model_name="post",
name="content_th_th",
field=models.TextField(
blank=True, help_text="post content", null=True, verbose_name="content"
),
),
migrations.AlterField(
model_name="post",
name="content_tr_tr",
field=models.TextField(
blank=True, help_text="post content", null=True, verbose_name="content"
),
),
migrations.AlterField(
model_name="post",
name="content_vi_vn",
field=models.TextField(
blank=True, help_text="post content", null=True, verbose_name="content"
),
),
migrations.AlterField(
model_name="post",
name="content_zh_hans",
field=models.TextField(
blank=True, help_text="post content", null=True, verbose_name="content"
),
),
]

View file

@ -0,0 +1,28 @@
# Generated by Django 5.2.11 on 2026-02-27 15:43
from django.db import migrations
import engine.core.utils.db
class Migration(migrations.Migration):
dependencies = [
("blog", "0008_alter_post_content_alter_post_content_ar_ar_and_more"),
]
operations = [
migrations.AlterField(
model_name="post",
name="slug",
field=engine.core.utils.db.TweakedAutoSlugField(
allow_unicode=True,
blank=True,
editable=False,
max_length=88,
null=True,
overwrite=True,
populate_from="title",
unique=True,
),
),
]

View file

@ -0,0 +1,17 @@
# Generated by Django 5.2.11 on 2026-03-01 22:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("blog", "0009_alter_post_slug"),
]
operations = [
migrations.AddField(
model_name="post",
name="blogpic",
field=models.ImageField(blank=True, null=True, upload_to="posts/images/"),
),
]

View file

@ -5,18 +5,20 @@ from django.db.models import (
CharField, CharField,
FileField, FileField,
ForeignKey, ForeignKey,
ImageField,
ManyToManyField, ManyToManyField,
TextField,
) )
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django_extensions.db.fields import AutoSlugField
from markdown.extensions.toc import TocExtension
from markdown_field import MarkdownField
from engine.core.abstract import NiceModel from engine.core.abstract import NiceModel
from engine.core.utils.db import TweakedAutoSlugField, unicode_slugify_function
from engine.core.utils.markdown import strip_markdown
class Post(NiceModel): # type: ignore [django-manager-missing] class Post(NiceModel):
__doc__ = _( # type: ignore [assignment] __doc__ = _( # pyright: ignore[reportUnknownVariableType]
"Represents a blog post model. " "Represents a blog post model. "
"The Post class defines the structure and behavior of a blog post. " "The Post class defines the structure and behavior of a blog post. "
"It includes attributes for author, title, content, optional file attachment, slug, and associated tags. " "It includes attributes for author, title, content, optional file attachment, slug, and associated tags. "
@ -41,46 +43,23 @@ class Post(NiceModel): # type: ignore [django-manager-missing]
help_text=_("post title"), help_text=_("post title"),
verbose_name=_("title"), verbose_name=_("title"),
) )
content: MarkdownField = MarkdownField( content = TextField(
"content", verbose_name=_("content"),
extensions=[ help_text=_("post content"),
TocExtension(toc_depth=3),
"pymdownx.arithmatex",
"pymdownx.b64",
"pymdownx.betterem",
"pymdownx.blocks.admonition",
"pymdownx.blocks.caption",
"pymdownx.blocks.definition",
"pymdownx.blocks.details",
"pymdownx.blocks.html",
"pymdownx.blocks.tab",
"pymdownx.caret",
"pymdownx.critic",
"pymdownx.emoji",
"pymdownx.escapeall",
"pymdownx.extra",
"pymdownx.fancylists",
"pymdownx.highlight",
"pymdownx.inlinehilite",
"pymdownx.keys",
"pymdownx.magiclink",
"pymdownx.mark",
"pymdownx.pathconverter",
"pymdownx.progressbar",
"pymdownx.saneheaders",
"pymdownx.smartsymbols",
"pymdownx.snippets",
"pymdownx.striphtml",
"pymdownx.superfences",
"pymdownx.tasklist",
"pymdownx.tilde",
],
blank=True, blank=True,
null=True, null=True,
) )
file = FileField(upload_to="posts/", blank=True, null=True) file = FileField(upload_to="posts/", blank=True, null=True)
slug = AutoSlugField( blogpic = ImageField(upload_to="posts/images/", blank=True, null=True)
populate_from="title", allow_unicode=True, unique=True, editable=False slug = TweakedAutoSlugField(
populate_from="title",
slugify_function=unicode_slugify_function,
allow_unicode=True,
unique=True,
editable=False,
max_length=88,
overwrite=True,
null=True,
) )
tags = ManyToManyField(to="blog.PostTag", blank=True, related_name="posts") tags = ManyToManyField(to="blog.PostTag", blank=True, related_name="posts")
meta_description = CharField(max_length=150, blank=True, null=True) meta_description = CharField(max_length=150, blank=True, null=True)
@ -95,22 +74,24 @@ class Post(NiceModel): # type: ignore [django-manager-missing]
def __str__(self): def __str__(self):
return f"{self.title} | {self.author.first_name} {self.author.last_name}" return f"{self.title} | {self.author.first_name} {self.author.last_name}"
@cached_property
def seo_description(self) -> str:
return strip_markdown(self.content or "")[:180]
class Meta: class Meta:
verbose_name = _("post") verbose_name = _("post")
verbose_name_plural = _("posts") verbose_name_plural = _("posts")
def save(self, **kwargs): def save(self, *args, **kwargs):
if self.file: if self.file:
raise ValueError( raise ValueError(
_("markdown files are not supported yet - use markdown content instead") _("file uploads are not supported yet - use content instead")
) )
if not any([self.file, self.content]) or all([self.file, self.content]): if not any([self.file, self.content]) or all([self.file, self.content]):
raise ValueError( raise ValueError(
_( _("a file or content must be provided - mutually exclusive")
"a markdown file or markdown content must be provided - mutually exclusive"
) )
) super().save(*args, **kwargs)
super().save(**kwargs)
class PostTag(NiceModel): class PostTag(NiceModel):

View file

@ -2,6 +2,7 @@ from rest_framework.fields import SerializerMethodField
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
from engine.blog.models import Post, PostTag from engine.blog.models import Post, PostTag
from engine.core.utils.markdown import render_markdown
class PostTagSerializer(ModelSerializer): class PostTagSerializer(ModelSerializer):
@ -19,4 +20,4 @@ class PostSerializer(ModelSerializer):
fields = "__all__" fields = "__all__"
def get_content(self, obj: Post) -> str: def get_content(self, obj: Post) -> str:
return obj.content.html.replace("\n", "<br/>") return render_markdown(obj.content or "")

View file

@ -7,12 +7,12 @@ from engine.blog.docs.drf.viewsets import POST_SCHEMA
from engine.blog.filters import PostFilter from engine.blog.filters import PostFilter
from engine.blog.models import Post from engine.blog.models import Post
from engine.blog.serializers import PostSerializer from engine.blog.serializers import PostSerializer
from engine.core.permissions import EvibesPermission from engine.core.permissions import SchonPermission
@extend_schema_view(**POST_SCHEMA) @extend_schema_view(**POST_SCHEMA)
class PostViewSet(ReadOnlyModelViewSet): # type: ignore [type-arg] class PostViewSet(ReadOnlyModelViewSet):
__doc__ = _( # type: ignore [assignment] __doc__ = _(
"Encapsulates operations for managing and retrieving Post entities in a read-only model view set. " "Encapsulates operations for managing and retrieving Post entities in a read-only model view set. "
"This class is tailored to handle Post objects that are active and allows filtration based on defined " "This class is tailored to handle Post objects that are active and allows filtration based on defined "
"filters. It integrates with Django's backend filtering system and ensures operations align with the " "filters. It integrates with Django's backend filtering system and ensures operations align with the "
@ -20,7 +20,7 @@ class PostViewSet(ReadOnlyModelViewSet): # type: ignore [type-arg]
) )
serializer_class = PostSerializer serializer_class = PostSerializer
permission_classes = (EvibesPermission,) permission_classes = (SchonPermission,)
queryset = Post.objects.filter(is_active=True) queryset = Post.objects.filter(is_active=True)
filter_backends = [DjangoFilterBackend] filter_backends = [DjangoFilterBackend]
filterset_class = PostFilter filterset_class = PostFilter

View file

@ -1,51 +0,0 @@
from typing import Any
from django import forms
from django.forms.renderers import BaseRenderer
from django.utils.safestring import mark_safe
class MarkdownEditorWidget(forms.Textarea):
class Media:
css = {
"all": (
"https://cdnjs.cloudflare.com/ajax/libs/easymde/2.14.0/easymde.min.css",
)
}
js = ("https://cdnjs.cloudflare.com/ajax/libs/easymde/2.14.0/easymde.min.js",)
def render(
self,
name: str,
value: str,
attrs: dict[Any, Any] | None = None,
renderer: BaseRenderer | None = None,
):
if not attrs:
attrs = {}
attrs["class"] = "markdown-editor"
textarea_html = super().render(name, value, attrs, renderer)
textarea_id = attrs.get("id", f"id_{name}")
init_js = f"""
<script>
document.addEventListener('DOMContentLoaded', function() {{
var el = document.getElementById("{textarea_id}");
if (!el || !window.EasyMDE) return;
new EasyMDE({{
element: el,
spellChecker: false,
renderingConfig: {{ singleLineBreaks: false }},
autoDownloadFontAwesome: false,
toolbar: [
"bold","italic","heading","|",
"quote","unordered-list","ordered-list","|",
"link","image","|",
"preview","side-by-side","fullscreen","|",
"guide"
]
}});
}});
</script>
"""
# noinspection DjangoSafeString
return mark_safe(textarea_html + init_js)

View file

@ -24,20 +24,20 @@ class NiceModel(Model):
) )
created = CreationDateTimeField( created = CreationDateTimeField(
_("created"), help_text=_("when the object first appeared on the database") _("created"), help_text=_("when the object first appeared on the database")
) # type: ignore [no-untyped-call] )
modified = ModificationDateTimeField( modified = ModificationDateTimeField(
_("modified"), help_text=_("when the object was last modified") _("modified"), help_text=_("when the object was last modified")
) # type: ignore [no-untyped-call] )
def save( # type: ignore [override] def save(
self, self,
*, *args,
force_insert: bool = False, force_insert: bool = False,
force_update: bool = False, force_update: bool = False,
using: str | None = None, using: str | None = None,
update_fields: Collection[str] | None = None, update_fields: Collection[str] | None = None,
update_modified: bool = True, update_modified: bool = True,
) -> None: ) -> None: # ty:ignore[invalid-method-override]
self.update_modified = update_modified self.update_modified = update_modified
return super().save( return super().save(
force_insert=force_insert, force_insert=force_insert,

View file

@ -1,16 +1,18 @@
from contextlib import suppress from contextlib import suppress
from typing import Any, ClassVar, Type from typing import Any, Callable, ClassVar, Type
from constance.admin import Config from constance.admin import Config
from constance.admin import ConstanceAdmin as BaseConstanceAdmin from constance.admin import ConstanceAdmin as BaseConstanceAdmin
from django.apps import AppConfig, apps from django.apps import AppConfig, apps
from django.conf import settings from django.conf import settings
from django.contrib import admin
from django.contrib.admin import register, site from django.contrib.admin import register, site
from django.contrib.gis.admin import GISModelAdmin from django.contrib.gis.admin import GISModelAdmin
from django.contrib.messages import constants as messages from django.contrib.messages import constants as messages
from django.db.models import Model from django.db.models import Model, TextField
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from django.http import HttpRequest from django.http import HttpRequest
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django_celery_beat.admin import ClockedScheduleAdmin as BaseClockedScheduleAdmin from django_celery_beat.admin import ClockedScheduleAdmin as BaseClockedScheduleAdmin
from django_celery_beat.admin import CrontabScheduleAdmin as BaseCrontabScheduleAdmin from django_celery_beat.admin import CrontabScheduleAdmin as BaseCrontabScheduleAdmin
@ -31,7 +33,9 @@ from mptt.admin import DraggableMPTTAdmin
from unfold.admin import ModelAdmin, TabularInline from unfold.admin import ModelAdmin, TabularInline
from unfold.contrib.import_export.forms import ExportForm, ImportForm from unfold.contrib.import_export.forms import ExportForm, ImportForm
from unfold.decorators import action from unfold.decorators import action
from unfold.typing import FieldsetsType
from unfold.widgets import UnfoldAdminSelectWidget, UnfoldAdminTextInputWidget from unfold.widgets import UnfoldAdminSelectWidget, UnfoldAdminTextInputWidget
from unfold_markdown import MarkdownWidget
from engine.core.forms import ( from engine.core.forms import (
CRMForm, CRMForm,
@ -53,6 +57,7 @@ from engine.core.models import (
Order, Order,
OrderCrmLink, OrderCrmLink,
OrderProduct, OrderProduct,
PastedImage,
Product, Product,
ProductImage, ProductImage,
ProductTag, ProductTag,
@ -70,9 +75,7 @@ class FieldsetsMixin:
additional_fields: list[str] | None = [] additional_fields: list[str] | None = []
model: ClassVar[Type[Model]] model: ClassVar[Type[Model]]
def get_fieldsets( def get_fieldsets(self, request: HttpRequest, obj: Any = None) -> FieldsetsType:
self, request: HttpRequest, obj: Any = None
) -> list[tuple[str, dict[str, list[str]]]]:
if request: if request:
pass pass
@ -82,8 +85,8 @@ class FieldsetsMixin:
fieldsets = [] fieldsets = []
def add_translations_fieldset( def add_translations_fieldset(
fss: list[tuple[str, dict[str, list[str]]]], fss: FieldsetsType,
) -> list[tuple[str, dict[str, list[str]]]]: ) -> FieldsetsType:
with suppress(NotRegistered): with suppress(NotRegistered):
transoptions = translator.get_options_for_model(self.model) transoptions = translator.get_options_for_model(self.model)
translation_fields = [] translation_fields = []
@ -95,7 +98,7 @@ class FieldsetsMixin:
_("translations"), _("translations"),
{"classes": ["tab"], "fields": translation_fields}, {"classes": ["tab"], "fields": translation_fields},
) )
] # type: ignore [list-item] ]
return fss return fss
if self.general_fields: if self.general_fields:
@ -140,12 +143,13 @@ class FieldsetsMixin:
ts.append(name) ts.append(name)
if ts: if ts:
fieldsets.append((_("timestamps"), {"classes": ["tab"], "fields": ts})) fieldsets.append((_("timestamps"), {"classes": ["tab"], "fields": ts}))
fieldsets = add_translations_fieldset(fieldsets) # type: ignore [arg-type, assignment] fieldsets = add_translations_fieldset(fieldsets)
return fieldsets # type: ignore [return-value] return fieldsets
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
class ActivationActionsMixin: class ActivationActionsMixin:
message_user: Callable
actions_on_top = True actions_on_top = True
actions_on_bottom = True actions_on_bottom = True
actions = [ actions = [
@ -155,23 +159,23 @@ class ActivationActionsMixin:
] ]
@action( @action(
description=_("activate selected %(verbose_name_plural)s").lower(), description=_("Activate selected %(verbose_name_plural)s"), # ty:ignore[invalid-argument-type]
permissions=["change"], permissions=["change"],
) )
def activate_selected(self, request: HttpRequest, queryset: QuerySet[Any]) -> None: def activate_selected(self, request: HttpRequest, queryset: QuerySet[Any]) -> None:
try: try:
queryset.update(is_active=True) queryset.update(is_active=True)
self.message_user( # type: ignore [attr-defined] self.message_user(
request=request, request=request,
message=_("selected items have been activated.").lower(), message=_("selected items have been activated.").lower().title(),
level=messages.SUCCESS, level=messages.SUCCESS,
) )
except Exception as e: except Exception as e:
self.message_user(request=request, message=str(e), level=messages.ERROR) # type: ignore [attr-defined] self.message_user(request=request, message=str(e), level=messages.ERROR)
@action( @action(
description=_("deactivate selected %(verbose_name_plural)s").lower(), description=_("Deactivate selected %(verbose_name_plural)s"), # ty:ignore[invalid-argument-type]
permissions=["change"], permissions=["change"],
) )
def deactivate_selected( def deactivate_selected(
@ -179,17 +183,17 @@ class ActivationActionsMixin:
) -> None: ) -> None:
try: try:
queryset.update(is_active=False) queryset.update(is_active=False)
self.message_user( # type: ignore [attr-defined] self.message_user(
request=request, request=request,
message=_("selected items have been deactivated.").lower(), message=_("selected items have been deactivated.").lower().title(),
level=messages.SUCCESS, level=messages.SUCCESS,
) )
except Exception as e: except Exception as e:
self.message_user(request=request, message=str(e), level=messages.ERROR) # type: ignore [attr-defined] self.message_user(request=request, message=str(e), level=messages.ERROR)
class AttributeValueInline(TabularInline): # type: ignore [type-arg] class AttributeValueInline(TabularInline):
model = AttributeValue model = AttributeValue
extra = 0 extra = 0
autocomplete_fields = ["attribute"] autocomplete_fields = ["attribute"]
@ -201,7 +205,7 @@ class AttributeValueInline(TabularInline): # type: ignore [type-arg]
return super().get_queryset(request).select_related("attribute", "product") return super().get_queryset(request).select_related("attribute", "product")
class ProductImageInline(TabularInline): # type: ignore [type-arg] class ProductImageInline(TabularInline):
model = ProductImage model = ProductImage
extra = 0 extra = 0
tab = True tab = True
@ -212,7 +216,7 @@ class ProductImageInline(TabularInline): # type: ignore [type-arg]
return super().get_queryset(request).select_related("product") return super().get_queryset(request).select_related("product")
class StockInline(TabularInline): # type: ignore [type-arg] class StockInline(TabularInline):
model = Stock model = Stock
extra = 0 extra = 0
form = StockForm form = StockForm
@ -224,7 +228,7 @@ class StockInline(TabularInline): # type: ignore [type-arg]
return super().get_queryset(request).select_related("vendor", "product") return super().get_queryset(request).select_related("vendor", "product")
class OrderProductInline(TabularInline): # type: ignore [type-arg] class OrderProductInline(TabularInline):
model = OrderProduct model = OrderProduct
extra = 0 extra = 0
readonly_fields = ("product", "quantity", "buy_price") readonly_fields = ("product", "quantity", "buy_price")
@ -242,7 +246,7 @@ class OrderProductInline(TabularInline): # type: ignore [type-arg]
) )
class CategoryChildrenInline(TabularInline): # type: ignore [type-arg] class CategoryChildrenInline(TabularInline):
model = Category model = Category
fk_name = "parent" fk_name = "parent"
extra = 0 extra = 0
@ -255,9 +259,9 @@ class CategoryChildrenInline(TabularInline): # type: ignore [type-arg]
@register(AttributeGroup) @register(AttributeGroup)
class AttributeGroupAdmin( class AttributeGroupAdmin(
DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin
): # type: ignore [misc, type-arg] ):
# noinspection PyClassVar # noinspection PyClassVar
model = AttributeGroup # type: ignore [misc] model = AttributeGroup
list_display = ( list_display = (
"name", "name",
"modified", "modified",
@ -281,9 +285,9 @@ class AttributeGroupAdmin(
@register(Attribute) @register(Attribute)
class AttributeAdmin( class AttributeAdmin(
DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin
): # type: ignore [misc, type-arg] ):
# noinspection PyClassVar # noinspection PyClassVar
model = Attribute # type: ignore [misc] model = Attribute
list_display = ( list_display = (
"name", "name",
"group", "group",
@ -320,9 +324,9 @@ class AttributeAdmin(
@register(AttributeValue) @register(AttributeValue)
class AttributeValueAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin): # type: ignore [misc, type-arg] class AttributeValueAdmin(FieldsetsMixin, ActivationActionsMixin, ModelAdmin):
# noinspection PyClassVar # noinspection PyClassVar
model = AttributeValue # type: ignore [misc] model = AttributeValue
list_display = ( list_display = (
"attribute", "attribute",
"value", "value",
@ -365,6 +369,7 @@ class CategoryAdmin(
): ):
# noinspection PyClassVar # noinspection PyClassVar
model = Category model = Category
formfield_overrides = {TextField: {"widget": MarkdownWidget}}
list_display = ( list_display = (
"indented_title", "indented_title",
"parent", "parent",
@ -413,18 +418,16 @@ class CategoryAdmin(
@register(Brand) @register(Brand)
class BrandAdmin( class BrandAdmin(
DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin
): # type: ignore [misc, type-arg] ):
# noinspection PyClassVar # noinspection PyClassVar
model = Brand # type: ignore [misc] model = Brand
formfield_overrides = {TextField: {"widget": MarkdownWidget}}
list_display = ( list_display = (
"name", "name",
"priority", "priority",
"is_active", "is_active",
) )
list_filter = ( list_filter = ("is_active",)
"categories",
"is_active",
)
search_fields = ( search_fields = (
"uuid", "uuid",
"name", "name",
@ -451,22 +454,29 @@ class ProductAdmin(
ActivationActionsMixin, ActivationActionsMixin,
ModelAdmin, ModelAdmin,
ImportExportModelAdmin, ImportExportModelAdmin,
): # type: ignore [misc, type-arg] ):
# noinspection PyClassVar # noinspection PyClassVar
model = Product # type: ignore [misc] model = Product
formfield_overrides = {TextField: {"widget": MarkdownWidget}}
actions = ActivationActionsMixin.actions + [
"export_to_marketplaces",
"ban_from_marketplaces",
]
list_display = ( list_display = (
"sku", "sku",
"name", "name",
"is_active", "is_active",
"export_to_marketplaces",
"has_images",
"category", "category",
"brand", "brand",
"price", "price",
"rating",
"modified", "modified",
) )
list_filter = ( list_filter = (
"is_active", "is_active",
"is_digital", "is_digital",
"is_updatable",
"stocks__vendor", "stocks__vendor",
"tags__name", "tags__name",
"created", "created",
@ -489,6 +499,7 @@ class ProductAdmin(
"uuid", "uuid",
"modified", "modified",
"created", "created",
"video_preview",
) )
autocomplete_fields = ( autocomplete_fields = (
"category", "category",
@ -508,12 +519,80 @@ class ProductAdmin(
"name", "name",
"partnumber", "partnumber",
"is_digital", "is_digital",
"export_to_marketplaces",
] ]
relation_fields = [ relation_fields = [
"category", "category",
"brand", "brand",
"tags", "tags",
] ]
additional_fields = [
"is_updatable",
"video",
"video_preview",
]
def video_preview(self, obj: Product):
if obj.video:
return format_html(
'<video controls style="max-width:100%;max-height:360px">'
'<source src="{}">'
"</video>",
obj.video.url,
)
return ""
video_preview.short_description = _("video preview") # ty:ignore[unresolved-attribute]
def has_images(self, obj: Product) -> bool:
return obj.has_images
has_images.boolean = True # ty:ignore[unresolved-attribute]
has_images.short_description = _("has images") # ty:ignore[unresolved-attribute]
@action(
description=_("Export selected %(verbose_name_plural)s to marketplaces' feeds"), # ty:ignore[invalid-argument-type]
permissions=["change"],
)
def export_to_marketplaces(
self, request: HttpRequest, queryset: QuerySet[Any]
) -> None:
try:
queryset.update(export_to_marketplaces=True)
self.message_user(
request=request,
message=_(
"selected %(verbose_name_plural)s have been marked for export."
)
.lower()
.title(),
level=messages.SUCCESS,
)
except Exception as e:
self.message_user(request=request, message=str(e), level=messages.ERROR)
@action(
description=_("Ban selected %(verbose_name_plural)s from marketplaces' feeds"), # ty:ignore[invalid-argument-type]
permissions=["change"],
)
def ban_from_marketplaces(
self, request: HttpRequest, queryset: QuerySet[Any]
) -> None:
try:
queryset.update(export_to_marketplaces=False)
self.message_user(
request=request,
message=_(
"selected %(verbose_name_plural)s have been banned from export."
)
.lower()
.title(),
level=messages.SUCCESS,
)
except Exception as e:
self.message_user(request=request, message=str(e), level=messages.ERROR)
def get_queryset(self, request): def get_queryset(self, request):
return ( return (
@ -532,9 +611,9 @@ class ProductAdmin(
@register(ProductTag) @register(ProductTag)
class ProductTagAdmin( class ProductTagAdmin(
DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin
): # type: ignore [misc, type-arg] ):
# noinspection PyClassVar # noinspection PyClassVar
model = ProductTag # type: ignore [misc] model = ProductTag
list_display = ("tag_name",) list_display = ("tag_name",)
search_fields = ("tag_name",) search_fields = ("tag_name",)
readonly_fields = ( readonly_fields = (
@ -552,9 +631,9 @@ class ProductTagAdmin(
@register(CategoryTag) @register(CategoryTag)
class CategoryTagAdmin( class CategoryTagAdmin(
DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin
): # type: ignore [misc, type-arg] ):
# noinspection PyClassVar # noinspection PyClassVar
model = CategoryTag # type: ignore [misc] model = CategoryTag
list_display = ( list_display = (
"name", "name",
"tag_name", "tag_name",
@ -580,9 +659,9 @@ class CategoryTagAdmin(
@register(Vendor) @register(Vendor)
class VendorAdmin( class VendorAdmin(
DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin
): # type: ignore [misc, type-arg] ):
# noinspection PyClassVar # noinspection PyClassVar
model = Vendor # type: ignore [misc] model = Vendor
list_display = ( list_display = (
"name", "name",
"markup_percent", "markup_percent",
@ -622,9 +701,9 @@ class VendorAdmin(
@register(Feedback) @register(Feedback)
class FeedbackAdmin( class FeedbackAdmin(
DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin
): # type: ignore [misc, type-arg] ):
# noinspection PyClassVar # noinspection PyClassVar
model = Feedback # type: ignore [misc] model = Feedback
list_display = ( list_display = (
"order_product", "order_product",
"rating", "rating",
@ -657,9 +736,9 @@ class FeedbackAdmin(
@register(Order) @register(Order)
class OrderAdmin( class OrderAdmin(
DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin
): # type: ignore [misc, type-arg] ):
# noinspection PyClassVar # noinspection PyClassVar
model = Order # type: ignore [misc] model = Order
list_display = ( list_display = (
"human_readable_id", "human_readable_id",
"user", "user",
@ -710,9 +789,9 @@ class OrderAdmin(
@register(OrderProduct) @register(OrderProduct)
class OrderProductAdmin( class OrderProductAdmin(
DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin
): # type: ignore [misc, type-arg] ):
# noinspection PyClassVar # noinspection PyClassVar
model = OrderProduct # type: ignore [misc] model = OrderProduct
list_display = ( list_display = (
"order", "order",
"product", "product",
@ -750,9 +829,9 @@ class OrderProductAdmin(
@register(PromoCode) @register(PromoCode)
class PromoCodeAdmin( class PromoCodeAdmin(
DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin
): # type: ignore [misc, type-arg] ):
# noinspection PyClassVar # noinspection PyClassVar
model = PromoCode # type: ignore [misc] model = PromoCode
list_display = ( list_display = (
"code", "code",
"discount_percent", "discount_percent",
@ -796,9 +875,10 @@ class PromoCodeAdmin(
@register(Promotion) @register(Promotion)
class PromotionAdmin( class PromotionAdmin(
DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin
): # type: ignore [misc, type-arg] ):
# noinspection PyClassVar # noinspection PyClassVar
model = Promotion # type: ignore [misc] model = Promotion
formfield_overrides = {TextField: {"widget": MarkdownWidget}}
list_display = ( list_display = (
"name", "name",
"discount_percent", "discount_percent",
@ -825,9 +905,9 @@ class PromotionAdmin(
@register(Stock) @register(Stock)
class StockAdmin( class StockAdmin(
DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin
): # type: ignore [misc, type-arg] ):
# noinspection PyClassVar # noinspection PyClassVar
model = Stock # type: ignore [misc] model = Stock
form = StockForm form = StockForm
list_display = ( list_display = (
"product", "product",
@ -859,8 +939,8 @@ class StockAdmin(
"is_active", "is_active",
"sku", "sku",
"quantity", "quantity",
"price",
"purchase_price", "purchase_price",
"price",
"digital_asset", "digital_asset",
] ]
additional_fields = [ additional_fields = [
@ -875,9 +955,9 @@ class StockAdmin(
@register(Wishlist) @register(Wishlist)
class WishlistAdmin( class WishlistAdmin(
DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin
): # type: ignore [misc, type-arg] ):
# noinspection PyClassVar # noinspection PyClassVar
model = Wishlist # type: ignore [misc] model = Wishlist
list_display = ( list_display = (
"user", "user",
"modified", "modified",
@ -903,9 +983,9 @@ class WishlistAdmin(
@register(ProductImage) @register(ProductImage)
class ProductImageAdmin( class ProductImageAdmin(
DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin DjangoQLSearchMixin, FieldsetsMixin, ActivationActionsMixin, ModelAdmin
): # type: ignore [misc, type-arg] ):
# noinspection PyClassVar # noinspection PyClassVar
model = ProductImage # type: ignore [misc] model = ProductImage
list_display = ( list_display = (
"alt", "alt",
"product", "product",
@ -938,10 +1018,42 @@ class ProductImageAdmin(
] ]
@register(PastedImage)
class PastedImageAdmin(ActivationActionsMixin, ModelAdmin):
list_display = ("name", "image_preview", "alt_text", "is_active", "created")
list_filter = ("is_active",)
search_fields = ("name", "alt_text")
readonly_fields = ("uuid", "created", "modified", "image_preview_large")
fieldsets = (
(None, {"fields": ("name", "image", "alt_text")}),
(_("Preview"), {"fields": ("image_preview_large",)}),
(_("Metadata"), {"fields": ("uuid", "is_active", "created", "modified")}),
)
@admin.display(description=_("preview"))
def image_preview(self, obj: PastedImage) -> str:
if obj.image:
return format_html(
'<img src="{}" style="max-height: 50px; max-width: 100px;" />',
obj.image.url,
)
return "-"
@admin.display(description=_("image preview"))
def image_preview_large(self, obj: PastedImage) -> str:
if obj.image:
return format_html(
'<img src="{}" style="max-height: 300px; max-width: 500px;" />',
obj.image.url,
)
return "-"
@register(Address) @register(Address)
class AddressAdmin(DjangoQLSearchMixin, FieldsetsMixin, GISModelAdmin): # type: ignore [misc] class AddressAdmin(DjangoQLSearchMixin, FieldsetsMixin, GISModelAdmin):
# noinspection PyClassVar # noinspection PyClassVar
model = Address # type: ignore [misc] model = Address
list_display = ( list_display = (
"street", "street",
"city", "city",
@ -949,16 +1061,10 @@ class AddressAdmin(DjangoQLSearchMixin, FieldsetsMixin, GISModelAdmin): # type:
"country", "country",
"user", "user",
) )
list_filter = ( # country and region are encrypted — DB-level filtering is not possible
"country", list_filter = ()
"region", # street, city, postal_code are encrypted — DB-level search is not possible
) search_fields = ("user__email",)
search_fields = (
"street",
"city",
"postal_code",
"user__email",
)
readonly_fields = ( readonly_fields = (
"uuid", "uuid",
"modified", "modified",
@ -991,9 +1097,9 @@ class AddressAdmin(DjangoQLSearchMixin, FieldsetsMixin, GISModelAdmin): # type:
@register(CustomerRelationshipManagementProvider) @register(CustomerRelationshipManagementProvider)
class CustomerRelationshipManagementProviderAdmin( class CustomerRelationshipManagementProviderAdmin(
DjangoQLSearchMixin, FieldsetsMixin, ModelAdmin DjangoQLSearchMixin, FieldsetsMixin, ModelAdmin
): # type: ignore [misc, type-arg] ):
# noinspection PyClassVar # noinspection PyClassVar
model = CustomerRelationshipManagementProvider # type: ignore [misc] model = CustomerRelationshipManagementProvider
list_display = ( list_display = (
"name", "name",
"default", "default",
@ -1020,9 +1126,9 @@ class CustomerRelationshipManagementProviderAdmin(
@register(OrderCrmLink) @register(OrderCrmLink)
class OrderCrmLinkAdmin(DjangoQLSearchMixin, FieldsetsMixin, ModelAdmin): # type: ignore [misc, type-arg] class OrderCrmLinkAdmin(DjangoQLSearchMixin, FieldsetsMixin, ModelAdmin):
# noinspection PyClassVar # noinspection PyClassVar
model = OrderCrmLink # type: ignore [misc] model = OrderCrmLink
list_display = ( list_display = (
"crm_lead_id", "crm_lead_id",
"order", "order",
@ -1083,12 +1189,10 @@ class ConstanceConfig:
_meta = Meta() _meta = Meta()
# noinspection PyTypeChecker site.unregister([Config]) # ty:ignore[invalid-argument-type]
site.unregister([Config]) site.register([ConstanceConfig], BaseConstanceAdmin) # ty:ignore[invalid-argument-type]
# noinspection PyTypeChecker
site.register([ConstanceConfig], BaseConstanceAdmin) # type: ignore [list-item]
site.site_title = settings.PROJECT_NAME site.site_title = settings.PROJECT_NAME
site.site_header = "eVibes" site.site_header = "Schon"
site.index_title = settings.PROJECT_NAME site.index_title = settings.PROJECT_NAME

View file

@ -1,6 +1,6 @@
from django.conf import settings from django.conf import settings
from django.http import FileResponse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, extend_schema, inline_serializer from drf_spectacular.utils import OpenApiParameter, extend_schema, inline_serializer
from rest_framework import status from rest_framework import status
from rest_framework.fields import CharField, DictField, JSONField, ListField from rest_framework.fields import CharField, DictField, JSONField, ListField
@ -32,6 +32,9 @@ CUSTOM_OPENAPI_SCHEMA = {
"OpenApi3 schema for this API. Format can be selected via content negotiation. " "OpenApi3 schema for this API. Format can be selected via content negotiation. "
"Language can be selected with Accept-Language and query parameter both." "Language can be selected with Accept-Language and query parameter both."
), ),
responses={
status.HTTP_200_OK: OpenApiTypes.OBJECT,
},
) )
} }
@ -176,7 +179,7 @@ DOWNLOAD_DIGITAL_ASSET_SCHEMA = {
], ],
summary=_("download a digital asset from purchased digital order"), summary=_("download a digital asset from purchased digital order"),
responses={ responses={
status.HTTP_200_OK: FileResponse, status.HTTP_200_OK: OpenApiTypes.BINARY,
status.HTTP_400_BAD_REQUEST: error, status.HTTP_400_BAD_REQUEST: error,
}, },
) )

View file

@ -1,7 +1,7 @@
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from drf_spectacular.types import OpenApiTypes from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, extend_schema from drf_spectacular.utils import OpenApiParameter, extend_schema, inline_serializer
from rest_framework import status from rest_framework import serializers, status
from engine.core.docs.drf import BASE_ERRORS from engine.core.docs.drf import BASE_ERRORS
from engine.core.serializers import ( from engine.core.serializers import (
@ -234,7 +234,7 @@ CATEGORY_SCHEMA = {
name="lookup_value", name="lookup_value",
location="path", location="path",
description=_("Category UUID or slug"), description=_("Category UUID or slug"),
type=str, type=OpenApiTypes.STR,
), ),
], ],
responses={status.HTTP_200_OK: CategoryDetailSerializer(), **BASE_ERRORS}, responses={status.HTTP_200_OK: CategoryDetailSerializer(), **BASE_ERRORS},
@ -284,7 +284,7 @@ CATEGORY_SCHEMA = {
name="lookup_value", name="lookup_value",
location="path", location="path",
description=_("Category UUID or slug"), description=_("Category UUID or slug"),
type=str, type=OpenApiTypes.STR,
), ),
], ],
responses={ responses={
@ -369,7 +369,7 @@ ORDER_SCHEMA = {
name="lookup_value", name="lookup_value",
location="path", location="path",
description=_("Order UUID or human-readable id"), description=_("Order UUID or human-readable id"),
type=str, type=OpenApiTypes.STR,
), ),
], ],
responses={status.HTTP_200_OK: OrderDetailSerializer(), **BASE_ERRORS}, responses={status.HTTP_200_OK: OrderDetailSerializer(), **BASE_ERRORS},
@ -750,6 +750,30 @@ PRODUCT_SCHEMA = {
**BASE_ERRORS, **BASE_ERRORS,
}, },
), ),
"exact_list": extend_schema(
tags=[
"products",
],
summary=_("retrieve exact products by identifier"),
description=_(
"retrieve a list of products by identifier type (uuid, slug, or sku). "
"Send a POST request with `identificator_type` and `identificators` (list of values)."
),
request=inline_serializer(
name="ExactProductsRequest",
fields={
"identificator_type": serializers.ChoiceField(
choices=["uuid", "slug", "sku"]
),
"identificators": serializers.ListField(child=serializers.CharField()),
},
),
parameters=[],
responses={
status.HTTP_200_OK: ProductSimpleSerializer(many=True),
**BASE_ERRORS,
},
),
"seo_meta": extend_schema( "seo_meta": extend_schema(
tags=[ tags=[
"products", "products",
@ -1006,7 +1030,7 @@ BRAND_SCHEMA = {
name="lookup_value", name="lookup_value",
location="path", location="path",
description=_("Brand UUID or slug"), description=_("Brand UUID or slug"),
type=str, type=OpenApiTypes.STR,
), ),
], ],
responses={status.HTTP_200_OK: BrandDetailSerializer(), **BASE_ERRORS}, responses={status.HTTP_200_OK: BrandDetailSerializer(), **BASE_ERRORS},
@ -1049,7 +1073,7 @@ BRAND_SCHEMA = {
name="lookup_value", name="lookup_value",
location="path", location="path",
description=_("Brand UUID or slug"), description=_("Brand UUID or slug"),
type=str, type=OpenApiTypes.STR,
), ),
], ],
responses={status.HTTP_200_OK: SeoSnapshotSerializer(), **BASE_ERRORS}, responses={status.HTTP_200_OK: SeoSnapshotSerializer(), **BASE_ERRORS},

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

View file

@ -1,25 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="100.000000pt" height="100.000000pt" viewBox="0 0 100.000000 100.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,100.000000) scale(0.100000,-0.100000)"
fill="#7965D1" stroke="none">
<path d="M678 935 c-73 -50 -88 -121 -38 -175 29 -31 50 -35 57 -13 2 6 -5 14
-16 18 -30 9 -26 48 9 88 63 72 130 72 149 -1 18 -67 -6 -117 -89 -182 -97
-76 -142 -97 -235 -109 -121 -16 -324 -29 -380 -24 -48 5 -49 4 -33 -13 26
-26 108 -34 248 -23 308 23 362 40 480 147 l65 59 0 64 c0 79 -17 114 -72 152
-61 41 -100 44 -145 12z"/>
<path d="M327 912 c-10 -10 -17 -27 -17 -38 0 -24 35 -64 55 -64 18 0 19 12 3
28 -16 16 19 54 46 50 17 -2 22 -11 24 -45 4 -55 -38 -105 -105 -124 -50 -14
-179 -17 -225 -6 -34 9 -36 -3 -6 -23 55 -35 251 -29 327 10 95 48 92 168 -6
219 -33 17 -78 13 -96 -7z"/>
<path d="M475 435 c-60 -8 -171 -19 -245 -25 -74 -7 -137 -14 -139 -16 -2 -2
9 -9 25 -16 35 -15 179 -13 309 3 50 7 146 12 215 13 186 2 223 -22 185 -119
-20 -53 -49 -78 -115 -100 -37 -12 -54 -14 -69 -5 -41 21 -16 91 36 105 27 6
27 7 9 21 -31 22 -69 17 -99 -14 -15 -15 -27 -34 -27 -42 0 -23 52 -90 81
-106 43 -22 73 -17 144 22 73 40 93 64 102 118 21 131 -138 193 -412 161z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -10,6 +10,7 @@ from django_elasticsearch_dsl import fields
from django_elasticsearch_dsl.registries import registry from django_elasticsearch_dsl.registries import registry
from elasticsearch import NotFoundError from elasticsearch import NotFoundError
from elasticsearch.dsl import Q, Search from elasticsearch.dsl import Q, Search
from elasticsearch.dsl.types import Hit
from rest_framework.request import Request from rest_framework.request import Request
from engine.core.models import Brand, Category, Product from engine.core.models import Brand, Category, Product
@ -199,8 +200,8 @@ def process_query(
minimum_should_match=1, minimum_should_match=1,
) )
def build_search(idxs: list[str], size: int) -> Search: def build_search(idxs: list[str], size: int) -> Search[Hit]:
return ( result: Search[Hit] = ( # ty: ignore[invalid-assignment]
Search(index=idxs) Search(index=idxs)
.query(query_base) .query(query_base)
.extra( .extra(
@ -222,6 +223,7 @@ def process_query(
) )
.extra(size=size, track_total_hits=True) .extra(size=size, track_total_hits=True)
) )
return result
resp_cats = None resp_cats = None
if "categories" in indexes: if "categories" in indexes:
@ -289,12 +291,12 @@ def process_query(
] ]
for qx in product_exact_sequence: for qx in product_exact_sequence:
try: try:
resp_exact = ( search_exact = (
Search(index=["products"]) Search(index=["products"])
.query(qx) .query(qx)
.extra(size=5, track_total_hits=False) .extra(size=5, track_total_hits=False)
.execute()
) )
resp_exact = search_exact.execute() # ty: ignore[possibly-missing-attribute]
except NotFoundError: except NotFoundError:
resp_exact = None resp_exact = None
if resp_exact is not None and getattr(resp_exact, "hits", None): if resp_exact is not None and getattr(resp_exact, "hits", None):
@ -309,7 +311,7 @@ def process_query(
.extra(size=5, track_total_hits=False) .extra(size=5, track_total_hits=False)
) )
try: try:
resp_exact = s_exact.execute() resp_exact = s_exact.execute() # ty: ignore[possibly-missing-attribute]
except NotFoundError: except NotFoundError:
resp_exact = None resp_exact = None
if resp_exact is not None and getattr(resp_exact, "hits", None): if resp_exact is not None and getattr(resp_exact, "hits", None):
@ -434,9 +436,9 @@ def _lang_analyzer(lang_code: str) -> str:
class ActiveOnlyMixin: class ActiveOnlyMixin:
def get_queryset(self) -> QuerySet[Any]: def get_queryset(self) -> QuerySet[Any]:
return super().get_queryset().filter(is_active=True) # type: ignore [no-any-return, misc] return super().get_queryset().filter(is_active=True) # type: ignore[misc]
def should_index_object(self, obj) -> bool: # type: ignore [no-untyped-def] def should_index_object(self, obj) -> bool:
return getattr(obj, "is_active", False) return getattr(obj, "is_active", False)
@ -665,7 +667,7 @@ def process_system_query(
.query(mm) .query(mm)
.extra(size=size_per_index, track_total_hits=False) .extra(size=size_per_index, track_total_hits=False)
) )
resp = s.execute() resp = s.execute() # ty: ignore[possibly-missing-attribute]
for h in resp.hits: for h in resp.hits:
name = getattr(h, "name", None) or getattr(h, "title", None) or "N/A" name = getattr(h, "name", None) or getattr(h, "title", None) or "N/A"
results[idx].append( results[idx].append(

View file

@ -1,9 +1,6 @@
from typing import Any
from django.db.models import Model, QuerySet from django.db.models import Model, QuerySet
from django_elasticsearch_dsl import Document, fields from django_elasticsearch_dsl import Document, fields
from django_elasticsearch_dsl.registries import registry from django_elasticsearch_dsl.registries import registry
from health_check.db.models import TestModel
from engine.core.elasticsearch import ( from engine.core.elasticsearch import (
COMMON_ANALYSIS, COMMON_ANALYSIS,
@ -13,7 +10,7 @@ from engine.core.elasticsearch import (
from engine.core.models import Brand, Category, Product from engine.core.models import Brand, Category, Product
class BaseDocument(Document): # type: ignore [misc] class BaseDocument(Document):
name = fields.TextField( name = fields.TextField(
attr="name", attr="name",
analyzer="standard", analyzer="standard",
@ -195,18 +192,3 @@ class BrandDocument(ActiveOnlyMixin, BaseDocument):
add_multilang_fields(BrandDocument) add_multilang_fields(BrandDocument)
registry.register_document(BrandDocument) registry.register_document(BrandDocument)
class TestModelDocument(Document): # type: ignore [misc]
class Index:
name = "testmodels"
class Django:
model = TestModel
fields = ["title"]
ignore_signals = True
related_models: list[Any] = []
auto_refresh = False
registry.register_document(TestModelDocument)

View file

@ -0,0 +1,21 @@
from engine.core.feeds.amazon_seller import AmazonSellerFeedGenerator
from engine.core.feeds.base import BaseFeedGenerator
from engine.core.feeds.google_merchant import GoogleMerchantFeedGenerator
from engine.core.feeds.yandex_market import YandexMarketFeedGenerator
from engine.core.feeds.yandex_products import YandexProductsFeedGenerator
FEED_GENERATORS: dict[str, type[BaseFeedGenerator]] = {
"google_merchant": GoogleMerchantFeedGenerator,
"yandex_market": YandexMarketFeedGenerator,
"yandex_products": YandexProductsFeedGenerator,
"amazon_seller": AmazonSellerFeedGenerator,
}
__all__ = [
"BaseFeedGenerator",
"GoogleMerchantFeedGenerator",
"YandexMarketFeedGenerator",
"YandexProductsFeedGenerator",
"AmazonSellerFeedGenerator",
"FEED_GENERATORS",
]

View file

@ -0,0 +1,241 @@
from datetime import datetime
from typing import Any
from xml.etree.ElementTree import Element, SubElement
from constance import config
from django.conf import settings
from django.db.models import QuerySet
from engine.core.feeds.base import BaseFeedGenerator
from engine.core.models import Product
class AmazonSellerFeedGenerator(BaseFeedGenerator):
"""
Amazon Seller Central feed generator.
Generates product feeds in Amazon's XML format for Seller Central.
Reference: https://developer-docs.amazon.com/sp-api/docs/feeds-api-v2021-06-30-reference
"""
name: str = "amazon_seller"
supported_formats: tuple[str, ...] = ("xml", "json", "yaml")
default_format: str = "xml"
AMAZON_NS = "http://www.amazon.com/schema/merchant/product/2024-01"
def generate_feed_data(self, products: QuerySet[Product]) -> dict[str, Any]:
"""Generate feed data as a structured dictionary."""
messages = []
for idx, product in enumerate(products, start=1):
message = self._build_message(product, idx)
if message:
messages.append(message)
return {
"header": {
"documentVersion": "1.0",
"merchantIdentifier": config.COMPANY_NAME or settings.PROJECT_NAME,
},
"messageType": "Product",
"purgeAndReplace": False,
"messages": messages,
"generated_at": datetime.now().isoformat(),
}
def _build_message(
self, product: Product, message_id: int
) -> dict[str, Any] | None:
"""Build a message dictionary for a product."""
if not product.price or product.price <= 0:
return None
images = self.get_product_images(product)
description_data: dict[str, Any] = {
"title": product.name[:200],
"brand": product.brand.name if product.brand else "",
"description": (product.description or "")[:2000],
"bulletPoint": self._get_bullet_points(product),
"manufacturer": product.brand.name
if product.brand
else config.COMPANY_NAME or "",
"itemType": self._get_item_type(product),
}
if images:
description_data["mainImage"] = {
"imageType": "Main",
"imageLocation": images[0],
}
if len(images) > 1:
description_data["otherImages"] = [
{"imageType": f"PT{i}", "imageLocation": img}
for i, img in enumerate(images[1:9], start=1)
]
message: dict[str, Any] = {
"messageID": message_id,
"operationType": "Update",
"product": {
"sku": product.sku,
"standardProductID": self._get_standard_product_id(product),
"productTaxCode": "A_GEN_TAX",
"descriptionData": description_data,
"productData": self._get_product_data(product),
},
}
return message
def _get_standard_product_id(self, product: Product) -> dict[str, str]:
"""Get standard product identifier (EAN/UPC/GTIN)."""
id_types = [
("ean", "EAN"),
("upc", "UPC"),
("gtin", "GTIN"),
("isbn", "ISBN"),
]
for attr_value in product.attributes.all():
attr_name_lower = attr_value.attribute.name.lower()
for attr_key, amazon_type in id_types:
if attr_name_lower == attr_key:
return {"type": amazon_type, "value": attr_value.value}
if product.partnumber:
return {"type": "PrivateLabel", "value": product.partnumber}
return {"type": "PrivateLabel", "value": product.sku}
def _get_bullet_points(self, product: Product) -> list[str]:
"""Generate bullet points from product attributes."""
bullet_points = []
for attr_value in product.attributes.all()[:5]:
bullet_points.append(f"{attr_value.attribute.name}: {attr_value.value}")
return bullet_points
def _get_item_type(self, product: Product) -> str:
"""Get the item type from category."""
if product.category:
return product.category.name
return "General"
def _get_product_data(self, product: Product) -> dict[str, Any]:
"""Get additional product data."""
data = {
"price": {
"standardPrice": {
"value": product.price,
"currency": self.get_currency(),
},
},
"inventory": {
"quantity": product.quantity,
"fulfillmentLatency": 3,
},
}
if product.discount_price:
sale_price = product.price - product.discount_price
if sale_price > 0:
data["price"]["salePrice"] = {
"value": sale_price,
"currency": self.get_currency(),
}
return data
def to_xml(self, data: dict[str, Any]) -> str:
"""Convert feed data to Amazon XML format."""
envelope = Element("AmazonEnvelope")
envelope.set("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
envelope.set("xsi:noNamespaceSchemaLocation", "amzn-envelope.xsd")
header = SubElement(envelope, "Header")
doc_version = SubElement(header, "DocumentVersion")
doc_version.text = data["header"]["documentVersion"]
merchant_id = SubElement(header, "MerchantIdentifier")
merchant_id.text = data["header"]["merchantIdentifier"]
message_type = SubElement(envelope, "MessageType")
message_type.text = data["messageType"]
purge = SubElement(envelope, "PurgeAndReplace")
purge.text = "true" if data["purgeAndReplace"] else "false"
for msg_data in data["messages"]:
self._add_message_to_xml(envelope, msg_data)
return '<?xml version="1.0" encoding="UTF-8"?>\n' + self.prettify_xml(envelope)
def _add_message_to_xml(self, envelope: Element, msg_data: dict[str, Any]) -> None:
"""Add a message to the XML envelope."""
message = SubElement(envelope, "Message")
message_id = SubElement(message, "MessageID")
message_id.text = str(msg_data["messageID"])
operation_type = SubElement(message, "OperationType")
operation_type.text = msg_data["operationType"]
product = SubElement(message, "Product")
product_data = msg_data["product"]
sku = SubElement(product, "SKU")
sku.text = product_data["sku"]
std_product_id = SubElement(product, "StandardProductID")
type_elem = SubElement(std_product_id, "Type")
type_elem.text = product_data["standardProductID"]["type"]
value_elem = SubElement(std_product_id, "Value")
value_elem.text = product_data["standardProductID"]["value"]
tax_code = SubElement(product, "ProductTaxCode")
tax_code.text = product_data["productTaxCode"]
desc_data = SubElement(product, "DescriptionData")
self._add_description_data_to_xml(desc_data, product_data["descriptionData"])
def _add_description_data_to_xml(
self, parent: Element, desc_data: dict[str, Any]
) -> None:
"""Add description data to the XML element."""
title = SubElement(parent, "Title")
title.text = desc_data["title"]
if desc_data.get("brand"):
brand = SubElement(parent, "Brand")
brand.text = desc_data["brand"]
if desc_data.get("description"):
description = SubElement(parent, "Description")
description.text = desc_data["description"]
for bullet in desc_data.get("bulletPoint", []):
bullet_point = SubElement(parent, "BulletPoint")
bullet_point.text = bullet
if desc_data.get("manufacturer"):
manufacturer = SubElement(parent, "Manufacturer")
manufacturer.text = desc_data["manufacturer"]
if desc_data.get("itemType"):
item_type = SubElement(parent, "ItemType")
item_type.text = desc_data["itemType"]
if desc_data.get("mainImage"):
main_image = SubElement(parent, "MainImage")
img_type = SubElement(main_image, "ImageType")
img_type.text = desc_data["mainImage"]["imageType"]
img_loc = SubElement(main_image, "ImageLocation")
img_loc.text = desc_data["mainImage"]["imageLocation"]
for other_img in desc_data.get("otherImages", []):
other_image = SubElement(parent, "OtherImage")
img_type = SubElement(other_image, "ImageType")
img_type.text = other_img["imageType"]
img_loc = SubElement(other_image, "ImageLocation")
img_loc.text = other_img["imageLocation"]

173
engine/core/feeds/base.py Normal file
View file

@ -0,0 +1,173 @@
import json
import logging
import os
from abc import ABC, abstractmethod
from datetime import datetime
from typing import Any
from xml.dom import minidom
from xml.etree.ElementTree import Element, tostring
import yaml
from django.conf import settings
from django.db.models import QuerySet
from engine.core.models import Product
logger = logging.getLogger(__name__)
class BaseFeedGenerator(ABC):
"""
Base class for marketplace feed generators.
Each marketplace feed generator should inherit from this class and implement
the required methods for generating feed data in the appropriate format.
"""
name: str = "base"
supported_formats: tuple[str, ...] = ("xml", "json", "yaml")
default_format: str = "xml"
def __init__(self, locale: str = "en-gb"):
self.locale = locale
self.generated_at = datetime.now()
def get_products(self) -> QuerySet[Product]:
"""Get products that should be exported to marketplaces."""
return (
Product.objects.filter(
is_active=True,
export_to_marketplaces=True,
)
.select_related(
"category",
"brand",
)
.prefetch_related(
"images",
"stocks",
"attributes__attribute",
"tags",
)
)
def get_product_url(self, product: Product) -> str:
"""Generate the frontend URL for a product."""
return (
f"https://{settings.STOREFRONT_DOMAIN}/{self.locale}/product/{product.slug}"
)
def get_product_image_url(self, product: Product) -> str:
"""Get the primary image URL for a product."""
image = product.images.order_by("priority").first()
if image:
return image.image_url
return ""
def get_product_images(self, product: Product) -> list[str]:
"""Get all image URLs for a product."""
return [
img.image_url
for img in product.images.order_by("priority")
if img.image_url
]
def get_availability(self, product: Product) -> str:
"""Get availability status for a product."""
return "in stock" if product.quantity > 0 else "out of stock"
def get_currency(self) -> str:
"""Get the currency code."""
return settings.CURRENCY_CODE
def get_output_path(self, format_type: str) -> str:
"""Get the output file path for the feed."""
feeds_dir = os.path.join(settings.MEDIA_ROOT, "feeds")
os.makedirs(feeds_dir, exist_ok=True)
extension = format_type if format_type != "yaml" else "yml"
return os.path.join(feeds_dir, f"{self.name}.{extension}")
@abstractmethod
def generate_feed_data(self, products: QuerySet[Product]) -> Any:
"""
Generate the feed data structure.
This method should be implemented by each marketplace-specific generator
to create the appropriate data structure for that marketplace.
"""
raise NotImplementedError
def to_xml(self, data: Any) -> str:
"""Convert feed data to XML format."""
raise NotImplementedError(
f"{self.__class__.__name__} does not support XML format"
)
def to_json(self, data: Any) -> str:
"""Convert feed data to JSON format."""
return json.dumps(data, ensure_ascii=False, indent=2)
def to_yaml(self, data: Any) -> str:
"""Convert feed data to YAML format."""
return yaml.dump(data, allow_unicode=True, default_flow_style=False)
def generate(self, format_type: str | None = None) -> str:
"""
Generate the feed and save it to a file.
Args:
format_type: The output format (xml, json, yaml). Defaults to the generator's default.
Returns:
The path to the generated feed file.
"""
if format_type is None:
format_type = self.default_format
if format_type not in self.supported_formats:
raise ValueError(
f"Format '{format_type}' is not supported by {self.__class__.__name__}. "
f"Supported formats: {self.supported_formats}"
)
products = self.get_products()
product_count = products.count()
if product_count == 0:
logger.warning("No products to export for %s feed", self.name)
logger.info("Generating %s feed with %d products", self.name, product_count)
feed_data = self.generate_feed_data(products)
match format_type:
case "xml":
content = self.to_xml(feed_data)
case "json":
content = self.to_json(feed_data)
case "yaml" | "yml":
content = self.to_yaml(feed_data)
case _:
raise ValueError(f"Unknown format: {format_type}")
output_path = self.get_output_path(format_type)
with open(output_path, "w", encoding="utf-8") as f:
f.write(content)
logger.info("Generated %s feed at %s", self.name, output_path)
return output_path
@staticmethod
def prettify_xml(elem: Element) -> str:
"""Return a pretty-printed XML string for the Element (without XML declaration)."""
rough_string = tostring(elem, encoding="unicode")
reparsed = minidom.parseString(rough_string)
pretty = reparsed.toprettyxml(indent=" ")
# Strip the XML declaration added by toprettyxml so callers can add their own
lines = pretty.split("\n")
if lines and lines[0].startswith("<?xml"):
lines = lines[1:]
return "\n".join(lines).strip()

View file

@ -0,0 +1,162 @@
from datetime import datetime
from typing import Any
from xml.etree.ElementTree import Element, SubElement
from constance import config
from django.conf import settings
from django.db.models import QuerySet
from engine.core.feeds.base import BaseFeedGenerator
from engine.core.models import Product
class GoogleMerchantFeedGenerator(BaseFeedGenerator):
"""
Google Merchant Center feed generator.
Generates product feeds in Atom/RSS format compatible with Google Shopping.
Reference: https://support.google.com/merchants/answer/7052112
"""
name: str = "google_merchant"
supported_formats: tuple[str, ...] = ("xml", "json")
default_format: str = "xml"
GOOGLE_NS = "http://base.google.com/ns/1.0"
def generate_feed_data(self, products: QuerySet[Product]) -> list[dict[str, Any]]:
"""Generate feed data as a list of product dictionaries."""
items = []
for product in products:
item = self._build_product_item(product)
if item:
items.append(item)
return items
def _build_product_item(self, product: Product) -> dict[str, Any] | None:
"""Build a product item dictionary for the feed."""
if not product.price or product.price <= 0:
return None
images = self.get_product_images(product)
primary_image = images[0] if images else ""
additional_images = images[1:10] if len(images) > 1 else []
item = {
"id": product.sku,
"title": product.name[:150],
"description": (product.description or "")[:5000],
"link": self.get_product_url(product),
"image_link": primary_image,
"availability": self.get_availability(product),
"price": f"{product.price:.2f} {self.get_currency()}",
"brand": product.brand.name if product.brand else "",
"condition": "new",
"product_type": self._get_product_type(product),
}
if additional_images:
item["additional_image_link"] = additional_images
if product.partnumber:
item["mpn"] = product.partnumber
if product.discount_price:
sale_price = product.price - product.discount_price
if sale_price > 0:
item["sale_price"] = f"{sale_price:.2f} {self.get_currency()}"
gtin = self._get_gtin(product)
if gtin:
item["gtin"] = gtin
else:
item["identifier_exists"] = "no"
return item
def _get_product_type(self, product: Product) -> str:
"""Build the product type hierarchy from category."""
if not product.category:
return ""
ancestors = product.category.get_ancestors(include_self=True)
return " > ".join([cat.name for cat in ancestors])
def _get_gtin(self, product: Product) -> str | None:
"""Extract GTIN/EAN/UPC from product attributes."""
gtin_names = ["gtin", "ean", "upc", "isbn", "barcode"]
for attr_value in product.attributes.all():
if attr_value.attribute.name.lower() in gtin_names:
return attr_value.value
return None
def to_xml(self, data: list[dict[str, Any]]) -> str:
"""Convert feed data to Google Merchant XML format."""
rss = Element("rss")
rss.set("version", "2.0")
rss.set("xmlns:g", self.GOOGLE_NS)
channel = SubElement(rss, "channel")
title = SubElement(channel, "title")
title.text = config.COMPANY_NAME or settings.PROJECT_NAME
link = SubElement(channel, "link")
link.text = f"https://{settings.STOREFRONT_DOMAIN}"
description = SubElement(channel, "description")
description.text = (
f"Product feed for {config.COMPANY_NAME or settings.PROJECT_NAME}"
)
for product_data in data:
item = SubElement(channel, "item")
self._add_product_to_xml(item, product_data)
return '<?xml version="1.0" encoding="UTF-8"?>\n' + self.prettify_xml(rss)
def _add_product_to_xml(self, item: Element, product_data: dict[str, Any]) -> None:
"""Add a product's data to an XML item element."""
simple_fields = [
("id", "g:id"),
("title", "g:title"),
("description", "g:description"),
("link", "g:link"),
("image_link", "g:image_link"),
("availability", "g:availability"),
("price", "g:price"),
("brand", "g:brand"),
("condition", "g:condition"),
("product_type", "g:product_type"),
("mpn", "g:mpn"),
("gtin", "g:gtin"),
("sale_price", "g:sale_price"),
("identifier_exists", "g:identifier_exists"),
]
for data_key, xml_tag in simple_fields:
if data_key in product_data and product_data[data_key]:
elem = SubElement(item, xml_tag)
elem.text = str(product_data[data_key])
additional_images = product_data.get("additional_image_link", [])
for img_url in additional_images:
elem = SubElement(item, "g:additional_image_link")
elem.text = img_url
def to_json(self, data: list[dict[str, Any]]) -> str:
"""Convert feed data to JSON format."""
feed = {
"channel": {
"title": config.COMPANY_NAME or settings.PROJECT_NAME,
"link": f"https://{settings.STOREFRONT_DOMAIN}",
"description": f"Product feed for {config.COMPANY_NAME or settings.PROJECT_NAME}",
"generated_at": datetime.now().isoformat(),
},
"items": data,
}
return super().to_json(feed)

Some files were not shown because too many files have changed in this diff Show more