bazis-author 2.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,19 @@
1
+ # Copyright 2026 EcoFuture Technology Services LLC and contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ try:
16
+ from importlib.metadata import PackageNotFoundError, version
17
+ __version__ = version('bazis-author')
18
+ except PackageNotFoundError:
19
+ __version__ = 'dev'
@@ -0,0 +1,34 @@
1
+ # file generated by setuptools-scm
2
+ # don't change, don't track in version control
3
+
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
12
+
13
+ TYPE_CHECKING = False
14
+ if TYPE_CHECKING:
15
+ from typing import Tuple
16
+ from typing import Union
17
+
18
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
20
+ else:
21
+ VERSION_TUPLE = object
22
+ COMMIT_ID = object
23
+
24
+ version: str
25
+ __version__: str
26
+ __version_tuple__: VERSION_TUPLE
27
+ version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
30
+
31
+ __version__ = version = '2.2.0'
32
+ __version_tuple__ = version_tuple = (2, 2, 0)
33
+
34
+ __commit_id__ = commit_id = None
@@ -0,0 +1,53 @@
1
+ # Copyright 2026 EcoFuture Technology Services LLC and contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from django.utils.translation import gettext_lazy as _
16
+
17
+ from admin_auto_filters.filters import AutocompleteFilterFactory
18
+
19
+ from bazis.core.utils.sets_order import OrderedSet
20
+
21
+
22
+ class AuthorAdminMixin:
23
+ def get_search_fields(self, request):
24
+ return tuple(OrderedSet(super().get_search_fields(request) + ('author__username',)))
25
+
26
+ def get_readonly_fields(self, request, obj=None):
27
+ return tuple(
28
+ OrderedSet(super().get_readonly_fields(request, obj) + ('author', 'author_updated'))
29
+ )
30
+
31
+ def get_list_filter(self, request):
32
+ return (AutocompleteFilterFactory(_('Author'), 'author'),) + super().get_list_filter(
33
+ request
34
+ )
35
+
36
+ def save_model(self, request, obj, form, change):
37
+ if not obj.author:
38
+ obj.author = request.user
39
+ else:
40
+ obj.author_updated = request.user
41
+ return super().save_model(request, obj, form, change)
42
+
43
+ def save_formset(self, request, form, formset, change):
44
+ instances = formset.save(commit=False)
45
+ for obj in formset.deleted_objects:
46
+ obj.delete()
47
+ for obj in instances:
48
+ if not obj.author:
49
+ obj.author = request.user
50
+ else:
51
+ obj.author_updated = request.user
52
+ obj.save()
53
+ formset.save_m2m()
@@ -0,0 +1,22 @@
1
+ # Copyright 2026 EcoFuture Technology Services LLC and contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from django.utils.translation import gettext_lazy as _
16
+
17
+ from bazis.core.utils.apps import BaseConfig
18
+
19
+
20
+ class AuthingConfig(BaseConfig):
21
+ name = 'bazis.contrib.author'
22
+ verbose_name = _('Author')
@@ -0,0 +1,22 @@
1
+ # SOME DESCRIPTIVE TITLE.
2
+ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3
+ # This file is distributed under the same license as the PACKAGE package.
4
+ # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
+ #
6
+ #, fuzzy
7
+ msgid ""
8
+ msgstr ""
9
+ "Project-Id-Version: PACKAGE VERSION\n"
10
+ "Report-Msgid-Bugs-To: \n"
11
+ "POT-Creation-Date: 2024-07-04 07:22+0000\n"
12
+ "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
+ "Language-Team: LANGUAGE <LL@li.org>\n"
15
+ "Language: \n"
16
+ "MIME-Version: 1.0\n"
17
+ "Content-Type: text/plain; charset=UTF-8\n"
18
+ "Content-Transfer-Encoding: 8bit\n"
19
+ "Plural-Forms: nplurals=2; plural=(n != 1);\n"
20
+ #: admin_abstract.py:20 apps.py:7
21
+ msgid "Author"
22
+ msgstr ""
@@ -0,0 +1,24 @@
1
+ # SOME DESCRIPTIVE TITLE.
2
+ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3
+ # This file is distributed under the same license as the PACKAGE package.
4
+ # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
+ #
6
+ #, fuzzy
7
+ msgid ""
8
+ msgstr ""
9
+ "Project-Id-Version: PACKAGE VERSION\n"
10
+ "Report-Msgid-Bugs-To: \n"
11
+ "POT-Creation-Date: 2024-07-04 07:22+0000\n"
12
+ "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
+ "Language-Team: LANGUAGE <LL@li.org>\n"
15
+ "Language: \n"
16
+ "MIME-Version: 1.0\n"
17
+ "Content-Type: text/plain; charset=UTF-8\n"
18
+ "Content-Transfer-Encoding: 8bit\n"
19
+ "Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
20
+ "n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || "
21
+ "(n%100>=11 && n%100<=14)? 2 : 3);\n"
22
+ #: admin_abstract.py:20 apps.py:7
23
+ msgid "Author"
24
+ msgstr "Автор"
@@ -0,0 +1,49 @@
1
+ # Copyright 2026 EcoFuture Technology Services LLC and contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from django.conf import settings
16
+ from django.contrib.gis.db import models
17
+
18
+ from bazis.contrib.users.models_abstract import UserMixin
19
+
20
+
21
+ class AuthorMixin(UserMixin):
22
+ """
23
+ Mixin that enables working with author fields in a model
24
+ """
25
+
26
+ author = models.ForeignKey(
27
+ settings.AUTH_USER_MODEL,
28
+ related_name='author_%(class)s',
29
+ blank=True,
30
+ null=True,
31
+ on_delete=models.SET_NULL,
32
+ )
33
+ author_updated = models.ForeignKey(
34
+ settings.AUTH_USER_MODEL,
35
+ related_name='author_updated_%(class)s',
36
+ blank=True,
37
+ null=True,
38
+ on_delete=models.SET_NULL,
39
+ )
40
+
41
+ class Meta:
42
+ abstract = True
43
+
44
+ def save(self, *args, **kwargs):
45
+ if author := self.CTX_USER_REQUEST.get():
46
+ if not self.author:
47
+ self.author = author
48
+ self.author_updated = author
49
+ super().save(*args, **kwargs)
@@ -0,0 +1,50 @@
1
+ # Copyright 2026 EcoFuture Technology Services LLC and contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from bazis.contrib.users.routes_abstract import UserRequiredRouteBase, UserRouteBase
16
+ from bazis.core.routes_abstract.jsonapi import JsonapiRouteBase
17
+ from bazis.core.schemas import ApiAction, CrudApiAction, SchemaFields
18
+
19
+ from .models_abstract import AuthorMixin
20
+
21
+
22
+ class AuthorRouteMixin(JsonapiRouteBase):
23
+ abstract: bool = True
24
+
25
+ fields: dict[ApiAction, SchemaFields] = {
26
+ CrudApiAction.CREATE: SchemaFields(
27
+ exclude={'author': None, 'author_updated': None},
28
+ ),
29
+ CrudApiAction.UPDATE: SchemaFields(
30
+ exclude={'author': None, 'author_updated': None},
31
+ ),
32
+ }
33
+
34
+ def hook_before_create(self, item: AuthorMixin):
35
+ if not self.inject.user.is_anonymous:
36
+ item.author = self.inject.user
37
+ super().hook_before_create(item)
38
+
39
+ def hook_before_update(self, item: AuthorMixin):
40
+ if not self.inject.user.is_anonymous:
41
+ item.author_updated = self.inject.user
42
+ super().hook_before_update(item)
43
+
44
+
45
+ class AuthorRouteBase(AuthorRouteMixin, UserRouteBase):
46
+ abstract: bool = True
47
+
48
+
49
+ class AuthorRequiredRouteBase(AuthorRouteMixin, UserRequiredRouteBase):
50
+ abstract: bool = True
@@ -0,0 +1,589 @@
1
+ Metadata-Version: 2.4
2
+ Name: bazis-author
3
+ Version: 2.2.0
4
+ Summary: Author management module for Bazis framework.
5
+ Author-email: Ilya Kharyn <ilya.tt07@gmail.com>
6
+ Maintainer-email: Ilya Kharyn <ilya.tt07@gmail.com>
7
+ Project-URL: Home, https://github.com/ecofuture-tech/bazis-author
8
+ Keywords: bazis,django,fastapi,pydantic,framework,jsonapi,author
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Framework :: Django
17
+ Classifier: Framework :: FastAPI
18
+ Requires-Python: >=3.12
19
+ Description-Content-Type: text/markdown
20
+ Requires-Dist: bazis
21
+ Requires-Dist: bazis-users
22
+ Provides-Extra: test
23
+ Requires-Dist: bazis-test-utils; extra == "test"
24
+ Provides-Extra: dev
25
+ Requires-Dist: ruff; extra == "dev"
26
+
27
+ # Bazis Author
28
+
29
+ [![PyPI version](https://img.shields.io/pypi/v/bazis-author.svg)](https://pypi.org/project/bazis-author/)
30
+ [![Python Versions](https://img.shields.io/pypi/pyversions/bazis-author.svg)](https://pypi.org/project/bazis-author/)
31
+ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
32
+
33
+ An extension package for Bazis that provides automatic authorship tracking for models (who created and who updated records).
34
+
35
+ ## Quick Start
36
+
37
+ ```bash
38
+ # Install the package
39
+ uv add bazis-author
40
+
41
+ # Create model with authorship tracking
42
+ from bazis.contrib.author.models_abstract import AuthorMixin
43
+ from bazis.core.models_abstract import DtMixin, UuidMixin, JsonApiMixin
44
+ from django.db import models
45
+
46
+ class Article(AuthorMixin, DtMixin, UuidMixin, JsonApiMixin):
47
+ """Article with author tracking"""
48
+ title = models.CharField(max_length=255)
49
+ content = models.TextField()
50
+
51
+ class Meta:
52
+ verbose_name = 'Article'
53
+ verbose_name_plural = 'Articles'
54
+
55
+ # Create route with automatic author population
56
+ from bazis.contrib.author.routes_abstract import AuthorRouteBase
57
+ from django.apps import apps
58
+
59
+ class ArticleRouteSet(AuthorRouteBase):
60
+ model = apps.get_model('myapp.Article')
61
+ ```
62
+
63
+ ## Table of Contents
64
+
65
+ - [Description](#description)
66
+ - [Requirements](#requirements)
67
+ - [Installation](#installation)
68
+ - [Core Components](#core-components)
69
+ - [Model Mixins](#model-mixins)
70
+ - [Base Routes](#base-routes)
71
+ - [Admin Mixins](#admin-mixins)
72
+ - [Usage](#usage)
73
+ - [Creating Model with Authorship](#creating-model-with-authorship)
74
+ - [Creating Route with Authorship](#creating-route-with-authorship)
75
+ - [Admin Setup](#admin-setup)
76
+ - [How It Works](#how-it-works)
77
+ - [Examples](#examples)
78
+ - [License](#license)
79
+ - [Links](#links)
80
+
81
+ ## Description
82
+
83
+ **Bazis Author** is an extension package for the Bazis framework that provides automatic authorship tracking for records. The package includes:
84
+
85
+ - **AuthorMixin** — mixin for models adding `author` (who created) and `author_updated` (who updated) fields
86
+ - **AuthorRouteBase** and **AuthorRequiredRouteBase** — base route classes with automatic author field population
87
+ - **AuthorAdminMixin** — admin mixin with convenient filters and readonly fields
88
+
89
+ The main advantage of the package is fully automatic author field population without the need to write additional code.
90
+
91
+ **This package requires `bazis` and `bazis-users` packages to be installed.**
92
+
93
+ ## Requirements
94
+
95
+ - **Python**: 3.12+
96
+ - **bazis**: latest version
97
+ - **bazis-users**: latest version
98
+ - **PostgreSQL**: 12+
99
+ - **Redis**: For caching
100
+
101
+ ## Installation
102
+
103
+ ### Using uv (recommended)
104
+
105
+ ```bash
106
+ uv add bazis-author
107
+ ```
108
+
109
+ ### Using pip
110
+
111
+ ```bash
112
+ pip install bazis-author
113
+ ```
114
+
115
+ ## Core Components
116
+
117
+ ### Model Mixins
118
+
119
+ #### AuthorMixin
120
+
121
+ Mixin for models adding authorship tracking fields.
122
+
123
+ **Location**: `bazis.contrib.author.models_abstract.AuthorMixin`
124
+
125
+ **Added fields**:
126
+
127
+ - `author` — Foreign Key to user who created the record (nullable)
128
+ - `author_updated` — Foreign Key to user who last updated the record (nullable)
129
+
130
+ **Features**:
131
+
132
+ - Inherits from `UserMixin`, has access to user context via `CTX_USER_REQUEST`
133
+ - Automatically populates `author` when creating record
134
+ - Automatically updates `author_updated` on every save
135
+ - Fields are optional (nullable), so records can be created without a user
136
+
137
+ **Example usage**:
138
+
139
+ ```python
140
+ from bazis.contrib.author.models_abstract import AuthorMixin
141
+ from bazis.core.models_abstract import DtMixin, UuidMixin, JsonApiMixin
142
+ from django.db import models
143
+
144
+ class Document(AuthorMixin, DtMixin, UuidMixin, JsonApiMixin):
145
+ """Document with authorship tracking"""
146
+ title = models.CharField('Title', max_length=255)
147
+ content = models.TextField('Content')
148
+
149
+ class Meta:
150
+ verbose_name = 'Document'
151
+ verbose_name_plural = 'Documents'
152
+ ```
153
+
154
+ ### Base Routes
155
+
156
+ #### AuthorRouteMixin
157
+
158
+ Base mixin for routes that automatically populate author fields.
159
+
160
+ **Location**: `bazis.contrib.author.routes_abstract.AuthorRouteMixin`
161
+
162
+ **Features**:
163
+
164
+ - Excludes `author` and `author_updated` fields from CREATE and UPDATE schemas (they are populated automatically)
165
+ - Uses `hook_before_create` and `hook_before_update` hooks to set author
166
+ - Sets author only for authenticated users
167
+
168
+ **Methods**:
169
+
170
+ - `hook_before_create(item)` — sets `author` before creating record
171
+ - `hook_before_update(item)` — sets `author_updated` before updating record
172
+
173
+ #### AuthorRouteBase
174
+
175
+ Base route class with authorship support for optional authentication.
176
+
177
+ **Location**: `bazis.contrib.author.routes_abstract.AuthorRouteBase`
178
+
179
+ **Inheritance**: `AuthorRouteMixin` + `UserRouteBase`
180
+
181
+ Use this class when endpoints are accessible to both authenticated and anonymous users.
182
+
183
+ **Example usage**:
184
+
185
+ ```python
186
+ from bazis.contrib.author.routes_abstract import AuthorRouteBase
187
+ from django.apps import apps
188
+
189
+ class ArticleRouteSet(AuthorRouteBase):
190
+ """Route for articles with automatic authorship"""
191
+ model = apps.get_model('myapp.Article')
192
+ ```
193
+
194
+ #### AuthorRequiredRouteBase
195
+
196
+ Base route class with authorship support for required authentication.
197
+
198
+ **Location**: `bazis.contrib.author.routes_abstract.AuthorRequiredRouteBase`
199
+
200
+ **Inheritance**: `AuthorRouteMixin` + `UserRequiredRouteBase`
201
+
202
+ Use this class when endpoints require mandatory authentication (return 401 for unauthenticated requests).
203
+
204
+ **Example usage**:
205
+
206
+ ```python
207
+ from bazis.contrib.author.routes_abstract import AuthorRequiredRouteBase
208
+ from django.apps import apps
209
+
210
+ class PrivateDocumentRouteSet(AuthorRequiredRouteBase):
211
+ """Route for private documents (authenticated only)"""
212
+ model = apps.get_model('myapp.PrivateDocument')
213
+ ```
214
+
215
+ ### Admin Mixins
216
+
217
+ #### AuthorAdminMixin
218
+
219
+ Mixin for Django admin classes adding convenient work with author fields.
220
+
221
+ **Location**: `bazis.contrib.author.admin_abstract.AuthorAdminMixin`
222
+
223
+ **Capabilities**:
224
+
225
+ - Adds `author__username` to search fields
226
+ - Makes `author` and `author_updated` fields readonly
227
+ - Adds autocomplete filter by author to list view
228
+ - Automatically populates author fields when saving through admin
229
+
230
+ **Example usage**:
231
+
232
+ ```python
233
+ from django.contrib import admin
234
+ from bazis.contrib.author.admin_abstract import AuthorAdminMixin
235
+ from bazis.core.admin_abstract import DtAdminMixin
236
+ from .models import Article
237
+
238
+ @admin.register(Article)
239
+ class ArticleAdmin(AuthorAdminMixin, DtAdminMixin, admin.ModelAdmin):
240
+ list_display = ('id', 'title', 'author', 'author_updated', 'dt_created')
241
+ search_fields = ('title', 'content')
242
+ ```
243
+
244
+ ## Usage
245
+
246
+ ### Creating Model with Authorship
247
+
248
+ ```python
249
+ from bazis.contrib.author.models_abstract import AuthorMixin
250
+ from bazis.core.models_abstract import DtMixin, UuidMixin, JsonApiMixin
251
+ from django.db import models
252
+
253
+ class Article(AuthorMixin, DtMixin, UuidMixin, JsonApiMixin):
254
+ """Article with automatic author tracking"""
255
+ title = models.CharField('Title', max_length=255)
256
+ content = models.TextField('Content')
257
+ is_published = models.BooleanField('Published', default=False)
258
+
259
+ class Meta:
260
+ verbose_name = 'Article'
261
+ verbose_name_plural = 'Articles'
262
+
263
+ def __str__(self):
264
+ return self.title
265
+ ```
266
+
267
+ ### Creating Route with Authorship
268
+
269
+ #### For public endpoints (optional authentication)
270
+
271
+ ```python
272
+ from bazis.contrib.author.routes_abstract import AuthorRouteBase
273
+ from django.apps import apps
274
+
275
+ class ArticleRouteSet(AuthorRouteBase):
276
+ """
277
+ Articles can be created by anonymous users.
278
+ If user is authenticated - author fields will be populated automatically.
279
+ """
280
+ model = apps.get_model('myapp.Article')
281
+ ```
282
+
283
+ **Behavior**:
284
+ - Anonymous user creates record → `author` and `author_updated` remain `null`
285
+ - Authenticated user creates record → `author` is automatically populated
286
+ - Any user updates record → `author_updated` is populated if user is authenticated
287
+
288
+ #### For private endpoints (required authentication)
289
+
290
+ ```python
291
+ from bazis.contrib.author.routes_abstract import AuthorRequiredRouteBase
292
+ from django.apps import apps
293
+
294
+ class PrivateDocumentRouteSet(AuthorRequiredRouteBase):
295
+ """
296
+ Documents are accessible only to authenticated users.
297
+ Anonymous users will get 401.
298
+ """
299
+ model = apps.get_model('myapp.PrivateDocument')
300
+ ```
301
+
302
+ **Behavior**:
303
+ - Anonymous user tries to create/update record → 401 Unauthorized
304
+ - Authenticated user creates record → `author` is automatically populated
305
+ - Authenticated user updates record → `author_updated` is automatically updated
306
+
307
+ ### Admin Setup
308
+
309
+ ```python
310
+ from django.contrib import admin
311
+ from bazis.contrib.author.admin_abstract import AuthorAdminMixin
312
+ from bazis.core.admin_abstract import DtAdminMixin
313
+ from .models import Article
314
+
315
+ @admin.register(Article)
316
+ class ArticleAdmin(AuthorAdminMixin, DtAdminMixin, admin.ModelAdmin):
317
+ list_display = ('id', 'title', 'author', 'author_updated', 'is_published', 'dt_created')
318
+ search_fields = ('title', 'content')
319
+ list_filter = ('is_published',)
320
+ readonly_fields = ('dt_created', 'dt_updated')
321
+ ```
322
+
323
+ **What AuthorAdminMixin provides**:
324
+
325
+ 1. **Autocomplete filter by author** in list view
326
+ 2. **Search by author username** (`author__username`)
327
+ 3. **Readonly fields** `author` and `author_updated` (cannot be edited)
328
+ 4. **Automatic population** when saving through admin
329
+
330
+ ## How It Works
331
+
332
+ ### In Models
333
+
334
+ When calling `save()` on a model with `AuthorMixin`:
335
+
336
+ 1. Check for user presence in `CTX_USER_REQUEST` context
337
+ 2. If user exists:
338
+ - When creating record (`author` is empty) → set `author`
339
+ - When updating record → update `author_updated`
340
+ 3. Call `super().save()` to save
341
+
342
+ ```python
343
+ def save(self, *args, **kwargs):
344
+ if author := self.CTX_USER_REQUEST.get():
345
+ if not self.author:
346
+ self.author = author
347
+ self.author_updated = author
348
+ super().save(*args, **kwargs)
349
+ ```
350
+
351
+ ### In Routes
352
+
353
+ When creating/updating record via API:
354
+
355
+ 1. **In CREATE**: `hook_before_create(item)` is triggered
356
+ - If user is NOT anonymous → set `item.author`
357
+
358
+ 2. **In UPDATE**: `hook_before_update(item)` is triggered
359
+ - If user is NOT anonymous → set `item.author_updated`
360
+
361
+ 3. Fields `author` and `author_updated` are **excluded** from CREATE/UPDATE schemas, so client cannot pass them
362
+
363
+ ```python
364
+ fields: dict[ApiAction, SchemaFields] = {
365
+ CrudApiAction.CREATE: SchemaFields(
366
+ exclude={'author': None, 'author_updated': None},
367
+ ),
368
+ CrudApiAction.UPDATE: SchemaFields(
369
+ exclude={'author': None, 'author_updated': None},
370
+ ),
371
+ }
372
+ ```
373
+
374
+ ### In Admin
375
+
376
+ When saving through Django Admin:
377
+
378
+ 1. `save_model()` — sets author from `request.user`
379
+ 2. `save_formset()` — sets author for inline models
380
+
381
+ ## Examples
382
+
383
+ ### Complete Application Example
384
+
385
+ **models.py**:
386
+ ```python
387
+ from bazis.contrib.author.models_abstract import AuthorMixin
388
+ from bazis.core.models_abstract import DtMixin, UuidMixin, JsonApiMixin
389
+ from django.db import models
390
+
391
+ class Article(AuthorMixin, DtMixin, UuidMixin, JsonApiMixin):
392
+ """Public article"""
393
+ title = models.CharField('Title', max_length=255)
394
+ content = models.TextField('Content')
395
+ is_published = models.BooleanField('Published', default=False)
396
+
397
+ class Meta:
398
+ verbose_name = 'Article'
399
+ verbose_name_plural = 'Articles'
400
+
401
+ class PrivateNote(AuthorMixin, DtMixin, UuidMixin, JsonApiMixin):
402
+ """Private note (authenticated only)"""
403
+ title = models.CharField('Title', max_length=255)
404
+ content = models.TextField('Content')
405
+
406
+ class Meta:
407
+ verbose_name = 'Note'
408
+ verbose_name_plural = 'Notes'
409
+ ```
410
+
411
+ **routes.py**:
412
+ ```python
413
+ from bazis.contrib.author.routes_abstract import AuthorRouteBase, AuthorRequiredRouteBase
414
+ from django.apps import apps
415
+
416
+ class ArticleRouteSet(AuthorRouteBase):
417
+ """Articles accessible to everyone, but author is tracked if user is authenticated"""
418
+ model = apps.get_model('myapp.Article')
419
+
420
+ class PrivateNoteRouteSet(AuthorRequiredRouteBase):
421
+ """Notes accessible only to authenticated users"""
422
+ model = apps.get_model('myapp.PrivateNote')
423
+ ```
424
+
425
+ **router.py**:
426
+ ```python
427
+ from bazis.core.routing import BazisRouter
428
+ from . import routes
429
+
430
+ router = BazisRouter(tags=['Content'])
431
+ router.register(routes.ArticleRouteSet.as_router())
432
+ router.register(routes.PrivateNoteRouteSet.as_router())
433
+ ```
434
+
435
+ **admin.py**:
436
+ ```python
437
+ from django.contrib import admin
438
+ from bazis.contrib.author.admin_abstract import AuthorAdminMixin
439
+ from bazis.core.admin_abstract import DtAdminMixin
440
+ from .models import Article, PrivateNote
441
+
442
+ @admin.register(Article)
443
+ class ArticleAdmin(AuthorAdminMixin, DtAdminMixin, admin.ModelAdmin):
444
+ list_display = ('id', 'title', 'author', 'is_published', 'dt_created')
445
+ search_fields = ('title', 'content')
446
+ list_filter = ('is_published',)
447
+
448
+ @admin.register(PrivateNote)
449
+ class PrivateNoteAdmin(AuthorAdminMixin, DtAdminMixin, admin.ModelAdmin):
450
+ list_display = ('id', 'title', 'author', 'dt_created')
451
+ search_fields = ('title', 'content')
452
+ ```
453
+
454
+ ### API Usage Examples
455
+
456
+ **Creating article by anonymous user**:
457
+ ```bash
458
+ POST /api/v1/content/article/
459
+ Content-Type: application/json
460
+
461
+ {
462
+ "data": {
463
+ "type": "myapp.article",
464
+ "attributes": {
465
+ "title": "My Article",
466
+ "content": "Article content",
467
+ "is_published": true
468
+ }
469
+ }
470
+ }
471
+
472
+ # Response: 201 Created
473
+ # author and author_updated will be null
474
+ ```
475
+
476
+ **Creating article by authenticated user**:
477
+ ```bash
478
+ POST /api/v1/content/article/
479
+ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
480
+ Content-Type: application/json
481
+
482
+ {
483
+ "data": {
484
+ "type": "myapp.article",
485
+ "attributes": {
486
+ "title": "My Article",
487
+ "content": "Article content",
488
+ "is_published": true
489
+ }
490
+ }
491
+ }
492
+
493
+ # Response: 201 Created
494
+ # author is automatically set to current user
495
+ ```
496
+
497
+ **Attempting to create private note without authentication**:
498
+ ```bash
499
+ POST /api/v1/content/private_note/
500
+ Content-Type: application/json
501
+
502
+ {
503
+ "data": {
504
+ "type": "myapp.privatenote",
505
+ "attributes": {
506
+ "title": "Secret Note",
507
+ "content": "Secret content"
508
+ }
509
+ }
510
+ }
511
+
512
+ # Response: 401 Unauthorized
513
+ ```
514
+
515
+ **Creating private note with authentication**:
516
+ ```bash
517
+ POST /api/v1/content/private_note/
518
+ Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
519
+ Content-Type: application/json
520
+
521
+ {
522
+ "data": {
523
+ "type": "myapp.privatenote",
524
+ "attributes": {
525
+ "title": "Secret Note",
526
+ "content": "Secret content"
527
+ }
528
+ }
529
+ }
530
+
531
+ # Response: 201 Created
532
+ # author is automatically set to current user
533
+ ```
534
+
535
+ ### Authorship Check in Responses
536
+
537
+ Fields `author` and `author_updated` are returned in the `relationships` block:
538
+
539
+ ```json
540
+ {
541
+ "data": {
542
+ "type": "myapp.article",
543
+ "id": "123e4567-e89b-12d3-a456-426614174000",
544
+ "attributes": {
545
+ "title": "My Article",
546
+ "content": "Article content",
547
+ "is_published": true
548
+ },
549
+ "relationships": {
550
+ "author": {
551
+ "data": {
552
+ "type": "users.user",
553
+ "id": "456e7890-e89b-12d3-a456-426614174000"
554
+ }
555
+ },
556
+ "author_updated": {
557
+ "data": {
558
+ "type": "users.user",
559
+ "id": "456e7890-e89b-12d3-a456-426614174000"
560
+ }
561
+ }
562
+ }
563
+ }
564
+ }
565
+ ```
566
+
567
+ ## License
568
+
569
+ Apache License 2.0
570
+
571
+ See [LICENSE](LICENSE) file for details.
572
+
573
+ ## Links
574
+
575
+ - [Bazis Documentation](https://github.com/ecofuture-tech/bazis) — main repository
576
+ - [Bazis Users](https://github.com/ecofuture-tech/bazis-users) — user management package
577
+ - [Bazis Author Repository](https://github.com/ecofuture-tech/bazis-author) — package repository
578
+ - [Issue Tracker](https://github.com/ecofuture-tech/bazis-author/issues) — report bugs or request features
579
+
580
+ ## Support
581
+
582
+ If you have questions or issues:
583
+ - Check the [Bazis documentation](https://github.com/ecofuture-tech/bazis)
584
+ - Search [existing issues](https://github.com/ecofuture-tech/bazis-author/issues)
585
+ - Create a [new issue](https://github.com/ecofuture-tech/bazis-author/issues/new) with detailed information
586
+
587
+ ---
588
+
589
+ Made with ❤️ by the Bazis team
@@ -0,0 +1,14 @@
1
+ bazis/contrib/author/__init__.py,sha256=GF_6m8rcR8n6hcDDb-LLoiJk9lR-FoaZZEzkg2q-rK8,780
2
+ bazis/contrib/author/_version.py,sha256=6OGz4a0gjMGlckPyPCNiJDWyFDO-tWO8O_ZNx4ajT2Y,704
3
+ bazis/contrib/author/admin_abstract.py,sha256=xYUi8TPJ4_ifFxWzkMDziPXP0sIirVlUg8WSNt4gghs,1938
4
+ bazis/contrib/author/apps.py,sha256=2AP3Po84KEgjKbjjc8waa9ccR4xalw4cWRkdG1KCebY,816
5
+ bazis/contrib/author/models_abstract.py,sha256=lYSoo6l3EKOjRerK5fxFzcWmbyj0cvHUqyfi51WFdjQ,1525
6
+ bazis/contrib/author/routes_abstract.py,sha256=mVC5PuSbQEcSr1GX3FpzyyjLsOXgBaRBSJmn-AXvcmc,1793
7
+ bazis/contrib/author/locale/en/LC_MESSAGES/django.mo,sha256=N1pb17IfLd0ASiKO8d68-B4ygSpDkhKOCs8YTzMXQo0,380
8
+ bazis/contrib/author/locale/en/LC_MESSAGES/django.po,sha256=q5JTOSSpmRY-nFh4-6HTpu_OpLdVqA7JgQEHfatBc-w,686
9
+ bazis/contrib/author/locale/ru/LC_MESSAGES/django.mo,sha256=ypfFf1v7X21aXoTlA16HGjykaNbh--si-73UHNXCW8Q,560
10
+ bazis/contrib/author/locale/ru/LC_MESSAGES/django.po,sha256=qkgoI90R73pNNtchLEgL6WC7vNTaMa91VzqFmEwi4nk,840
11
+ bazis_author-2.2.0.dist-info/METADATA,sha256=njWDVKMMGvsB5WIuR205j5TXwgR00hT8ww6atJ55yRY,16937
12
+ bazis_author-2.2.0.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
13
+ bazis_author-2.2.0.dist-info/top_level.txt,sha256=WgdrPZTZBMG8i_EqxA3vU5qI4ETQ_RsqKqSqsfIApHY,6
14
+ bazis_author-2.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ bazis