accrete 0.0.112__py3-none-any.whl → 0.0.113__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.
File without changes
@@ -0,0 +1,43 @@
1
+ import django.apps
2
+ from django.contrib import admin
3
+ from django import forms
4
+ from . import models
5
+
6
+ installed_models = django.apps.apps.get_models(
7
+ include_auto_created=True, include_swapped=True
8
+ )
9
+ model_choices = [(
10
+ f'{m._meta.app_label}.{m._meta.model_name}',
11
+ f'{m._meta.app_label}.{m._meta.verbose_name}'
12
+ ) for m in installed_models]
13
+
14
+
15
+ class LogConfigForm(forms.ModelForm):
16
+
17
+ model = forms.ChoiceField(choices=model_choices)
18
+
19
+
20
+ class LogConfigFieldInLine(admin.TabularInline):
21
+
22
+ model = models.LogConfigField
23
+
24
+
25
+ class LogConfigAdmin(admin.ModelAdmin):
26
+
27
+ model = models.LogConfig
28
+ list_display = ('model', 'ignore_errors')
29
+ search_fields = ('pk', 'model')
30
+ list_filter = ['ignore_errors']
31
+ form = LogConfigForm
32
+ inlines = [LogConfigFieldInLine]
33
+
34
+ def formfield_for_choice_field(self, db_field, request, **kwargs):
35
+ if db_field.name == "model":
36
+ kwargs["choices"] = [
37
+ ("accepted", "Accepted"),
38
+ ("denied", "Denied"),
39
+ ]
40
+ return super().formfield_for_choice_field(db_field, request, **kwargs)
41
+
42
+
43
+ admin.site.register(models.LogConfig, LogConfigAdmin)
@@ -0,0 +1,13 @@
1
+ from django.apps import AppConfig
2
+ from django.db.models.signals import post_save
3
+
4
+
5
+ class LogConfig(AppConfig):
6
+ default_auto_field = 'django.db.models.BigAutoField'
7
+ name = 'accrete.contrib.log'
8
+
9
+ def ready(self):
10
+ from . import signals
11
+ post_save.connect(
12
+ signals.create_log, weak=False, dispatch_uid="accrete.contrib.log"
13
+ )
@@ -0,0 +1,79 @@
1
+ from django.db import models
2
+ from django.db.models.query import RawQuerySet
3
+ from django.forms import model_to_dict
4
+
5
+ from .models import Log, LogConfig
6
+
7
+ TYPES_FK = ['AutoField', 'BigAutoField', 'ForeignKey']
8
+ TYPES_INT = ['IntegerField', 'PositiveSmallIntegerField']
9
+ TYPES_DECIMAL = ['DecimalField']
10
+ TYPES_FLOAT = ['FloatField']
11
+ TYPES_STR = ['CharField', 'TextField']
12
+ TYPES_BOOL = ['BooleanField']
13
+ TYPES_DATETIME = ['DateTimeField']
14
+ TYPES_DATE = ['DateField']
15
+ TYPES_TIME = ['TimeField']
16
+
17
+
18
+ def internal_type_to_log_type(field: models.Field):
19
+ internal_type = field.get_internal_type()
20
+ if internal_type in TYPES_FK:
21
+ return 'fk'
22
+ if internal_type in TYPES_INT:
23
+ return 'int'
24
+ if internal_type in TYPES_FLOAT:
25
+ return 'float'
26
+ if internal_type in TYPES_DECIMAL:
27
+ return 'decimal'
28
+ if internal_type in TYPES_BOOL:
29
+ return 'bool'
30
+ if internal_type in TYPES_STR:
31
+ return 'str'
32
+ if internal_type in TYPES_DATE:
33
+ return 'date'
34
+ if internal_type in TYPES_DATETIME:
35
+ return 'datetime'
36
+
37
+
38
+ def log_state_to_dict(logs: RawQuerySet) -> tuple[dict, dict]:
39
+ state = dict()
40
+ info = dict()
41
+ if not logs:
42
+ return state, info
43
+ for log in logs:
44
+ state.update({log.field: log.cast_value()})
45
+ info.update({log.field: {'new_value_type': log.new_value_type}})
46
+ return state, info
47
+
48
+
49
+ def log_value_to_instance_value(log: Log):
50
+ if log.new_value_type == 'bool':
51
+ return bool(log.new_value == 'True')
52
+
53
+ if log.new_value == '':
54
+ return None
55
+ if log.new_value_type in ['fk', 'int']:
56
+ return int(log.new_value)
57
+
58
+
59
+ def get_instance_state(instance: models.Model):
60
+ state = model_to_dict(instance)
61
+ cleaned_state = dict()
62
+ log_config = LogConfig.objects.filter(
63
+ model=f'{instance._meta.app_label}.{instance._meta.model_name}'
64
+ ).prefetch_related('fields').first()
65
+ if not log_config:
66
+ return cleaned_state
67
+ all_fields = list(map(lambda x: x.name, instance._meta.get_fields()))
68
+ if log_config.exclude_fields:
69
+ fields_to_log = list(f for f in all_fields if f not in log_config.fields.all().values_list('field_name', flat=True))
70
+ else:
71
+ fields_to_log = log_config.fields.all().values_list('field_name', flat=True)
72
+
73
+ for f, v in state.items():
74
+ if isinstance(v, (list, tuple, dict)):
75
+ continue
76
+ if f not in fields_to_log:
77
+ continue
78
+ cleaned_state.update({f: v})
79
+ return cleaned_state
@@ -0,0 +1,38 @@
1
+ # Generated by Django 5.1.1 on 2025-01-08 20:04
2
+
3
+ import django.db.models.deletion
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ initial = True
10
+
11
+ dependencies = [
12
+ ('accrete', '0002_initial'),
13
+ ]
14
+
15
+ operations = [
16
+ migrations.CreateModel(
17
+ name='Log',
18
+ fields=[
19
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20
+ ('model', models.CharField(help_text='Combination of app label and model name seperated by a dot', max_length=255, verbose_name='Model')),
21
+ ('field', models.CharField(max_length=255, verbose_name='Field')),
22
+ ('object_id', models.BigIntegerField(verbose_name='Object ID')),
23
+ ('log_date', models.DateTimeField(auto_now_add=True, verbose_name='Date')),
24
+ ('old_value_type', models.CharField(choices=[('fk', 'Foreign Key'), ('int', 'Integer'), ('float', 'Float'), ('decimal', 'Decimal'), ('bool', 'Boolean'), ('str', 'String')], max_length=100, verbose_name='Old Value Type')),
25
+ ('new_value_type', models.CharField(choices=[('fk', 'Foreign Key'), ('int', 'Integer'), ('float', 'Float'), ('decimal', 'Decimal'), ('bool', 'Boolean'), ('str', 'String')], max_length=100, verbose_name='New Value Type')),
26
+ ('old_value', models.TextField(blank=True, null=True, verbose_name='Old Value')),
27
+ ('new_value', models.TextField(blank=True, null=True, verbose_name='New Value')),
28
+ ('tenant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='accrete.tenant', verbose_name='Tenant')),
29
+ ],
30
+ options={
31
+ 'verbose_name': 'Log',
32
+ 'verbose_name_plural': 'Logs',
33
+ 'db_table': 'accrete_log',
34
+ 'indexes': [models.Index(fields=['model', 'object_id'], name='accrete_log_model_cf888c_idx')],
35
+ 'constraints': [models.UniqueConstraint(fields=('model', 'field', 'object_id', 'log_date'), name='unique_group_fields')],
36
+ },
37
+ ),
38
+ ]
@@ -0,0 +1,31 @@
1
+ # Generated by Django 5.1.1 on 2025-01-10 07:56
2
+
3
+ import django.db.models.deletion
4
+ from django.conf import settings
5
+ from django.db import migrations, models
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+
10
+ dependencies = [
11
+ ('log', '0001_initial'),
12
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
13
+ ]
14
+
15
+ operations = [
16
+ migrations.AddField(
17
+ model_name='log',
18
+ name='user',
19
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='logs', to=settings.AUTH_USER_MODEL),
20
+ ),
21
+ migrations.AlterField(
22
+ model_name='log',
23
+ name='new_value_type',
24
+ field=models.CharField(choices=[('fk', 'Foreign Key'), ('int', 'Integer'), ('float', 'Float'), ('decimal', 'Decimal'), ('bool', 'Boolean'), ('str', 'String'), ('date', 'Date'), ('datetime', 'Date Time')], max_length=100, verbose_name='New Value Type'),
25
+ ),
26
+ migrations.AlterField(
27
+ model_name='log',
28
+ name='old_value_type',
29
+ field=models.CharField(choices=[('fk', 'Foreign Key'), ('int', 'Integer'), ('float', 'Float'), ('decimal', 'Decimal'), ('bool', 'Boolean'), ('str', 'String'), ('date', 'Date'), ('datetime', 'Date Time')], max_length=100, verbose_name='Old Value Type'),
30
+ ),
31
+ ]
@@ -0,0 +1,20 @@
1
+ # Generated by Django 5.1.1 on 2025-01-10 08:02
2
+
3
+ import django.db.models.deletion
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ('accrete', '0003_remove_member_name'),
11
+ ('log', '0002_log_user_alter_log_new_value_type_and_more'),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.AlterField(
16
+ model_name='log',
17
+ name='tenant',
18
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='accrete.tenant', verbose_name='Tenant'),
19
+ ),
20
+ ]
@@ -0,0 +1,47 @@
1
+ # Generated by Django 5.1.1 on 2025-01-10 15:20
2
+
3
+ import django.db.models.deletion
4
+ import django.db.models.functions.text
5
+ from django.db import migrations, models
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+
10
+ dependencies = [
11
+ ('log', '0003_alter_log_tenant'),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.CreateModel(
16
+ name='LogConfig',
17
+ fields=[
18
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
19
+ ('model', models.CharField(help_text='Combination of app label and model name seperated by a dot', max_length=255, verbose_name='Model')),
20
+ ('ignore_errors', models.BooleanField(default=False, help_text='IF true, exceptions during log creation will be ignored', verbose_name='Ignore Errors')),
21
+ ],
22
+ options={
23
+ 'verbose_name': 'Log Configuration',
24
+ 'verbose_name_plural': 'Log Configs',
25
+ 'db_table': 'accrete_log_config',
26
+ 'ordering': [django.db.models.functions.text.Lower('model')],
27
+ 'indexes': [models.Index(fields=['model'], name='accrete_log_model_a89071_idx')],
28
+ 'constraints': [models.UniqueConstraint(fields=('model',), name='unique_model')],
29
+ },
30
+ ),
31
+ migrations.CreateModel(
32
+ name='LogConfigField',
33
+ fields=[
34
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
35
+ ('field_name', models.CharField(help_text='Name of the field to log', max_length=255, verbose_name='Field Name')),
36
+ ('log_config', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='log.logconfig', verbose_name='Log Configuration')),
37
+ ],
38
+ options={
39
+ 'verbose_name': 'Log Configuration Field',
40
+ 'verbose_name_plural': 'Log Configuration Fields',
41
+ 'db_table': 'accrete_log_config_field',
42
+ 'ordering': [django.db.models.functions.text.Lower('field_name')],
43
+ 'indexes': [models.Index(fields=['log_config'], name='accrete_log_log_con_22f45e_idx')],
44
+ 'constraints': [models.UniqueConstraint(fields=('log_config', 'field_name'), name='unique_config_field_name')],
45
+ },
46
+ ),
47
+ ]
@@ -0,0 +1,29 @@
1
+ # Generated by Django 5.1.1 on 2025-01-12 09:24
2
+
3
+ import django.db.models.deletion
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ('log', '0004_logconfig_logconfigfield'),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AddField(
15
+ model_name='logconfig',
16
+ name='exclude_fields',
17
+ field=models.BooleanField(default=False, help_text='If set, Log Configuration Fields will be excluded from logging. Otherwise only configured fields will be logged.', verbose_name='Exclude Fields'),
18
+ ),
19
+ migrations.AlterField(
20
+ model_name='logconfig',
21
+ name='ignore_errors',
22
+ field=models.BooleanField(default=False, help_text='If set, exceptions during log creation will be ignored', verbose_name='Ignore Errors'),
23
+ ),
24
+ migrations.AlterField(
25
+ model_name='logconfigfield',
26
+ name='log_config',
27
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='fields', to='log.logconfig', verbose_name='Log Configuration'),
28
+ ),
29
+ ]
File without changes
@@ -0,0 +1,229 @@
1
+ from decimal import Decimal
2
+ from datetime import date, datetime
3
+
4
+ from django.conf import settings
5
+ from django.db.models.functions import Lower
6
+ from django.utils.translation import gettext_lazy as _
7
+ from django.db import models
8
+
9
+
10
+ class Log(models.Model):
11
+
12
+ class Meta:
13
+ verbose_name = _('Log')
14
+ verbose_name_plural = _('Logs')
15
+ db_table = 'accrete_log'
16
+ indexes = [
17
+ models.Index(fields=['model', 'object_id'])
18
+ ]
19
+ constraints = [
20
+ models.UniqueConstraint(
21
+ name='unique_group_fields',
22
+ fields=('model', 'field', 'object_id', 'log_date')
23
+ )
24
+ ]
25
+
26
+ tenant = models.ForeignKey(
27
+ verbose_name=_('Tenant'),
28
+ to='accrete.Tenant',
29
+ on_delete=models.CASCADE,
30
+ null=True,
31
+ blank=True
32
+ )
33
+
34
+ model = models.CharField(
35
+ verbose_name=_('Model'),
36
+ max_length=255,
37
+ help_text='Combination of app label and model name seperated by a dot'
38
+ )
39
+
40
+ field = models.CharField(
41
+ verbose_name=_('Field'),
42
+ max_length=255
43
+ )
44
+
45
+ object_id = models.BigIntegerField(
46
+ verbose_name='Object ID'
47
+ )
48
+
49
+ log_date = models.DateTimeField(
50
+ verbose_name=_('Date'),
51
+ auto_now_add=True
52
+ )
53
+
54
+ old_value_type = models.CharField(
55
+ verbose_name=_('Old Value Type'),
56
+ choices=[
57
+ ('fk', 'Foreign Key'),
58
+ ('int', 'Integer'),
59
+ ('float', 'Float'),
60
+ ('decimal', 'Decimal'),
61
+ ('bool', 'Boolean'),
62
+ ('str', 'String'),
63
+ ('date', 'Date'),
64
+ ('datetime', 'Date Time')
65
+ ],
66
+ max_length=100,
67
+ )
68
+
69
+ new_value_type = models.CharField(
70
+ verbose_name=_('New Value Type'),
71
+ choices=[
72
+ ('fk', 'Foreign Key'),
73
+ ('int', 'Integer'),
74
+ ('float', 'Float'),
75
+ ('decimal', 'Decimal'),
76
+ ('bool', 'Boolean'),
77
+ ('str', 'String'),
78
+ ('date', 'Date'),
79
+ ('datetime', 'Date Time')
80
+ ],
81
+ max_length=100
82
+ )
83
+
84
+ old_value = models.TextField(
85
+ verbose_name=_('Old Value'),
86
+ null=True,
87
+ blank=True
88
+ )
89
+
90
+ new_value = models.TextField(
91
+ verbose_name=_('New Value'),
92
+ null=True,
93
+ blank=True
94
+ )
95
+
96
+ user = models.ForeignKey(
97
+ to=settings.AUTH_USER_MODEL,
98
+ related_name='logs',
99
+ on_delete=models.SET_NULL,
100
+ null=True,
101
+ blank=True
102
+ )
103
+
104
+ def __str__(self):
105
+ return f'{self.model}.{self.field},{self.object_id}'
106
+
107
+ def cast_value(self):
108
+ if self.new_value is None:
109
+ return None
110
+ if self.new_value_type == 'fk':
111
+ return self.cast_fk()
112
+ if self.new_value_type == 'int':
113
+ return self.cast_int()
114
+ if self.new_value_type == 'float':
115
+ return self.cast_float()
116
+ if self.new_value_type == 'decimal':
117
+ return self.cast_decimal()
118
+ if self.new_value_type == 'bool':
119
+ return self.cast_bool()
120
+ if self.new_value_type == 'str':
121
+ return self.cast_str()
122
+ if self.new_value_type == 'date':
123
+ return self.cast_date()
124
+ if self.new_value_type == 'datetime':
125
+ return self.cast_date_time()
126
+
127
+ def cast_fk(self):
128
+ return self.cast_int()
129
+
130
+ def cast_int(self):
131
+ if self.new_value == '':
132
+ return None
133
+ return int(self.new_value)
134
+
135
+ def cast_float(self):
136
+ if self.new_value == '':
137
+ return 0.0
138
+ return float(self.new_value)
139
+
140
+ def cast_decimal(self):
141
+ if self.new_value == '':
142
+ return Decimal(0)
143
+ return Decimal(self.new_value)
144
+
145
+ def cast_bool(self):
146
+ return self.new_value == 'True'
147
+
148
+ def cast_str(self):
149
+ return self.new_value
150
+
151
+ def cast_date(self):
152
+ if self.new_value == '':
153
+ return None
154
+ return date.fromisoformat(str(self.new_value))
155
+
156
+ def cast_date_time(self):
157
+ if self.new_value == '':
158
+ return None
159
+ return datetime.fromisoformat(self.new_value)
160
+
161
+
162
+ class LogConfig(models.Model):
163
+
164
+ class Meta:
165
+ verbose_name = _('Log Configuration')
166
+ verbose_name_plural = _('Log Configs')
167
+ db_table = 'accrete_log_config'
168
+ ordering = [Lower('model')]
169
+ indexes = [
170
+ models.Index(fields=['model'])
171
+ ]
172
+ constraints = [
173
+ models.UniqueConstraint(
174
+ name='unique_model',
175
+ fields=('model',)
176
+ )
177
+ ]
178
+
179
+ model = models.CharField(
180
+ verbose_name=_('Model'),
181
+ max_length=255,
182
+ help_text=_('Combination of app label and model name seperated by a dot')
183
+ )
184
+
185
+ ignore_errors = models.BooleanField(
186
+ verbose_name=_('Ignore Errors'),
187
+ default=False,
188
+ help_text=_('If set, exceptions during log creation will be ignored')
189
+ )
190
+
191
+ exclude_fields = models.BooleanField(
192
+ verbose_name=_('Exclude Fields'),
193
+ default=False,
194
+ help_text=_(
195
+ 'If set, Log Configuration Fields will be excluded from logging. '
196
+ 'Otherwise only configured fields will be logged.'
197
+ )
198
+ )
199
+
200
+
201
+ class LogConfigField(models.Model):
202
+
203
+ class Meta:
204
+ verbose_name = _('Log Configuration Field')
205
+ verbose_name_plural = _('Log Configuration Fields')
206
+ db_table = 'accrete_log_config_field'
207
+ ordering = [Lower('field_name')]
208
+ indexes = [
209
+ models.Index(fields=['log_config'])
210
+ ]
211
+ constraints = [
212
+ models.UniqueConstraint(
213
+ name='unique_config_field_name',
214
+ fields=('log_config', 'field_name')
215
+ )
216
+ ]
217
+
218
+ log_config = models.ForeignKey(
219
+ verbose_name=_('Log Configuration'),
220
+ to='log.LogConfig',
221
+ related_name='fields',
222
+ on_delete=models.CASCADE
223
+ )
224
+
225
+ field_name = models.CharField(
226
+ verbose_name=_('Field Name'),
227
+ max_length=255,
228
+ help_text=_('Name of the field to log')
229
+ )
@@ -0,0 +1,34 @@
1
+ from .models import Log
2
+ from accrete.tenant import get_tenant
3
+
4
+
5
+ def current_log_state(model: str, object_id: int):
6
+ tenant = get_tenant()
7
+ tenant_id = tenant and tenant.id or None
8
+ logs = Log.objects.raw("""
9
+ SELECT
10
+ log_value.id,
11
+ log_value.model,
12
+ log_value.field,
13
+ log_value.object_id,
14
+ log_value.log_date,
15
+ log_value.old_value_type,
16
+ log_value.new_value_type,
17
+ log_value.old_value,
18
+ log_value.new_value
19
+ FROM (
20
+ SELECT model, field, object_id, MAX(log_date) AS log_date
21
+ FROM accrete_log
22
+ WHERE
23
+ model = %s AND
24
+ object_id = %s AND
25
+ tenant_id = %s
26
+ GROUP BY model, field, object_id
27
+ ) log_result
28
+ JOIN accrete_log log_value ON
29
+ log_value.model = log_result.model AND
30
+ log_value.field = log_result.field AND
31
+ log_value.object_id = log_result.object_id AND
32
+ log_result.log_date = log_value.log_date;
33
+ """, [model, object_id, tenant_id])
34
+ return logs
@@ -0,0 +1,62 @@
1
+ import logging
2
+
3
+ from django.db import models
4
+
5
+ from accrete.tenant import get_member, get_tenant
6
+ from accrete.utils.log import log_time
7
+ from .models import Log, LogConfig
8
+ from . import helper, queries
9
+
10
+ _logger = logging.getLogger(__name__)
11
+
12
+
13
+ @log_time
14
+ def create_log(sender, **kwargs):
15
+
16
+ def _create_log():
17
+ member = get_member()
18
+ user = member and member.user or None
19
+ object_id = instance.id
20
+ log_rows = queries.current_log_state(model, object_id)
21
+ log_state, log_info = helper.log_state_to_dict(log_rows)
22
+ instance_state = helper.get_instance_state(instance)
23
+ diff = dict(log_state.items() ^ instance_state.items())
24
+ logs_to_create = []
25
+ for field, value in instance_state.items():
26
+ if field not in diff.keys():
27
+ continue
28
+ value_type = helper.internal_type_to_log_type(instance._meta.get_field(field))
29
+ if value_type is None:
30
+ continue
31
+ if value is None:
32
+ new_value = None
33
+ else:
34
+ new_value = str(value)
35
+ log = Log(
36
+ model=model,
37
+ field=field,
38
+ object_id=instance.id,
39
+ old_value_type=log_info.get(field, {}).get('new_value_type', ''),
40
+ old_value=log_state.get(field),
41
+ new_value_type=helper.internal_type_to_log_type(instance._meta.get_field(field)),
42
+ new_value=new_value,
43
+ user=user,
44
+ tenant=get_tenant()
45
+ )
46
+ logs_to_create.append(log)
47
+ Log.objects.bulk_create(logs_to_create)
48
+
49
+ instance: models.Model = kwargs.get('instance')
50
+ model = f'{instance._meta.app_label}.{instance._meta.model_name}'
51
+ log_config = LogConfig.objects.filter(model=model).first()
52
+ if not log_config:
53
+ return
54
+
55
+ try:
56
+ _create_log()
57
+ except Exception as e:
58
+ _logger.exception(e)
59
+ if log_config.ignore_errors:
60
+ pass
61
+ else:
62
+ raise e
@@ -0,0 +1,3 @@
1
+ from django.test import TestCase
2
+
3
+ # Create your tests here.
@@ -0,0 +1,3 @@
1
+ from django.shortcuts import render
2
+
3
+ # Create your views here.
@@ -0,0 +1,11 @@
1
+ {% load i18n %}
2
+
3
+ {% if form.non_field_errors or form.save_error %}
4
+ <div class="notification is-danger is-light">
5
+ {{ form.non_field_errors }}
6
+ {% if form.save_error %}
7
+ <p>{{ form.save_error }}</p>
8
+ <span>{% translate 'Error ID' %}: {{ form.save_error_id }}</span>
9
+ {% endif %}
10
+ </div>
11
+ {% endif %}
accrete/managers.py CHANGED
@@ -40,3 +40,10 @@ class TenantManager(models.Manager):
40
40
  update_conflicts=update_conflicts, update_fields=update_fields,
41
41
  unique_fields=unique_fields
42
42
  )
43
+
44
+
45
+ class MemberManager(TenantManager):
46
+
47
+ def get_queryset(self):
48
+ queryset = super().get_queryset().select_related('tenant', 'user')
49
+ return queryset
@@ -0,0 +1,17 @@
1
+ # Generated by Django 5.1.1 on 2025-01-10 07:57
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('accrete', '0002_initial'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.RemoveField(
14
+ model_name='member',
15
+ name='name',
16
+ ),
17
+ ]
accrete/models.py CHANGED
@@ -3,7 +3,7 @@ from django.conf import settings
3
3
  from django.utils.translation import gettext_lazy as _
4
4
  from django.contrib.auth.validators import UnicodeUsernameValidator
5
5
  from accrete.tenant import get_tenant
6
- from accrete.managers import TenantManager
6
+ from accrete.managers import TenantManager, MemberManager
7
7
 
8
8
 
9
9
  class TenantModel(models.Model):
@@ -86,8 +86,6 @@ class Member(models.Model):
86
86
  ordering = ['tenant', 'user']
87
87
  db_table = 'accrete_member'
88
88
 
89
- username_validator = UnicodeUsernameValidator()
90
-
91
89
  user = models.ForeignKey(
92
90
  to=settings.AUTH_USER_MODEL,
93
91
  related_name='memberships',
@@ -100,18 +98,6 @@ class Member(models.Model):
100
98
  on_delete=models.CASCADE
101
99
  )
102
100
 
103
- name = models.CharField(
104
- verbose_name=_('Name'),
105
- max_length=150,
106
- help_text=_(
107
- '150 characters or fewer.'
108
- 'Letters, digits and @/./+/-/_ only.'
109
- ),
110
- blank=True,
111
- null=True,
112
- validators=[username_validator],
113
- )
114
-
115
101
  is_active = models.BooleanField(
116
102
  verbose_name=_('Active'),
117
103
  default=True
@@ -123,10 +109,10 @@ class Member(models.Model):
123
109
  through_fields=('member', 'access_group')
124
110
  )
125
111
 
126
- objects = TenantManager()
112
+ objects = MemberManager()
127
113
 
128
114
  def __str__(self):
129
- return f'{self.name or self.user}'
115
+ return f'{self.user}'
130
116
 
131
117
 
132
118
  class AccessGroup(models.Model):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: accrete
3
- Version: 0.0.112
3
+ Version: 0.0.113
4
4
  Summary: Django Shared Schema Multi Tenant
5
5
  Author-email: Benedikt Jilek <benedikt.jilek@pm.me>
6
6
  License: Copyright (c) 2023 Benedikt Jilek
@@ -3,15 +3,30 @@ accrete/admin.py,sha256=MUYUmCFlGYPowiXTbwl4_Q6Cq0-neiL53WW4P76JCLs,1174
3
3
  accrete/apps.py,sha256=F7ynMLHJr_6bRujWtZVUzCliY2CGKiDvyUmL4F68L2E,146
4
4
  accrete/config.py,sha256=eJUbvyBO3DvAD6xkVKjTAzlXy7V7EK9bVyb91girfUs,299
5
5
  accrete/forms.py,sha256=H2hPQemslRLvTVV0Wl1TfUmTc5wU3Z98nQTMiLMliqo,1288
6
- accrete/managers.py,sha256=3wylSG4XBL9s2hrIWJ8FHIL75PbuYgPZg5Svacwj2-Y,1454
6
+ accrete/managers.py,sha256=DevRVm7cStvlfz6TriitSINr40POCi4HNaHX48VkrMA,1620
7
7
  accrete/middleware.py,sha256=YN73WloNkN01oel9Dcj3xyhurcWoB6zMV0NT3hY8DGw,2264
8
- accrete/models.py,sha256=6vymvqZ-ygaIZ5r-P1F57twy17VxdRQbzr8PMLwsWxs,5453
8
+ accrete/models.py,sha256=k6BLNeQY4H_-WiODLpMoNJp_g3Dm4sTfeNj5n68oJ88,5109
9
9
  accrete/storage.py,sha256=Jp3oE_uPMqgarjS_G49KDFrR2eSe4XuIJK9oAF_QBxk,1288
10
10
  accrete/tenant.py,sha256=-__rFtYrc5SbxFX2-Dvw6Wc1XzSAD6DeIBRuOVQlbWQ,1908
11
11
  accrete/tests.py,sha256=Agltbzwwh5htvq_Qi9vqvxutzmg_GwgPS_N19xJZRlw,7197
12
12
  accrete/urls.py,sha256=goDFR-yhOlLLy7AMi9pmh2aBkxdtZtwXNg6mwI2zPhU,227
13
13
  accrete/views.py,sha256=dwgXLkIjqjG16vlG4C6QeS191HoDnT7bZtkLahJB6wQ,2582
14
14
  accrete/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ accrete/contrib/log/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ accrete/contrib/log/admin.py,sha256=a3VCS2_YmahBShewMS9yn1QOln-3rmCJqMsa6VnNqdU,1151
17
+ accrete/contrib/log/apps.py,sha256=O0Cje3MmpxPToJVgO195lBg0tRCy9Ou87-ntcdGBKM0,369
18
+ accrete/contrib/log/helper.py,sha256=Zzmwxz7vP0jdW2LCw8gDpPNBkqlELaCydz-nxGh7F5Y,2493
19
+ accrete/contrib/log/models.py,sha256=PUGtm2mmZIhWf7Idpu9hZ-1mJtKU2p975e7eoCZ2cHE,6082
20
+ accrete/contrib/log/queries.py,sha256=JMI_q6dQ0JjyORtfRPOW92of45TFxqPiwIhHEEar34o,1126
21
+ accrete/contrib/log/signals.py,sha256=Gsb7B9nRhLgQQ8qxtbCEnvgHkFK9v7JfYJfAupFQ21I,2036
22
+ accrete/contrib/log/tests.py,sha256=mrbGGRNg5jwbTJtWWa7zSKdDyeB4vmgZCRc2nk6VY-g,60
23
+ accrete/contrib/log/views.py,sha256=xc1IQHrsij7j33TUbo-_oewy3vs03pw_etpBWaMYJl0,63
24
+ accrete/contrib/log/migrations/0001_initial.py,sha256=wkGMfaYc09dOuJXY0JengDBTkS98rMutUzmEAb6eK3E,2130
25
+ accrete/contrib/log/migrations/0002_log_user_alter_log_new_value_type_and_more.py,sha256=E3IUq4P62sapYzbKbUuufdCbN4O4kRphpcyVfUpJwrc,1346
26
+ accrete/contrib/log/migrations/0003_alter_log_tenant.py,sha256=yp3WizRdAPvPctMiCmKcsqVix3a0LaIRmuAB6LJ6Gb0,582
27
+ accrete/contrib/log/migrations/0004_logconfig_logconfigfield.py,sha256=iXD3kDj8GAD5Dce9xVozl0n8KCQaK4LsQkLHR3BDZ28,2367
28
+ accrete/contrib/log/migrations/0005_logconfig_exclude_fields_and_more.py,sha256=eN0AG2vR80-9NvMBfijjM04qPfRGxO7B8VFabrEZ_ZQ,1127
29
+ accrete/contrib/log/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
30
  accrete/contrib/sequence/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
31
  accrete/contrib/sequence/admin.py,sha256=mTjab5cVklRUIQcSrsUo-_JgtXEsSdcFj_gfWhlStS4,273
17
32
  accrete/contrib/sequence/apps.py,sha256=2SalOz9piCrbOPudCh0grN1eojN9kEC4-jcNzBmfqEk,164
@@ -303,6 +318,7 @@ accrete/contrib/ui/templates/django/forms/widgets/select.html,sha256=uSfDpOQox2m
303
318
  accrete/contrib/ui/templates/django/forms/widgets/text.html,sha256=MSmLlQc7PsPoDLVtTOOiWNprrsPriNr712yFxaHyDIo,47
304
319
  accrete/contrib/ui/templates/django/forms/widgets/textarea.html,sha256=c9BTedqb3IkXLyVYd0p9pR8DFnsXCNGoxVBWZTk_Fic,278
305
320
  accrete/contrib/ui/templates/ui/content_right.html,sha256=XUF1tYpSKfO9FleYtJ2QmWPmwdLYxLHXdBLRa-BrFUs,221
321
+ accrete/contrib/ui/templates/ui/form_error.html,sha256=uA8FLdZyeU0vXJHlGK3rcBqcmXb63MLPV32uQyUTak4,348
306
322
  accrete/contrib/ui/templates/ui/layout.html,sha256=PVqvIPYdWPX8IuAes1db0SWlw05xu0SNp8vmV9n1Sms,12889
307
323
  accrete/contrib/ui/templates/ui/list.html,sha256=NY8DmHGl3n5O1u-_B9a_mlAck19ZmpYthzecADuc3BM,2250
308
324
  accrete/contrib/ui/templates/ui/list_update.html,sha256=mLQTCgkKfVI5jrgei-Upc1u87iXL0Q63uLzXHPwMyeo,110
@@ -356,6 +372,7 @@ accrete/contrib/user_registration/templates/user_registration/registration.html,
356
372
  accrete/contrib/user_registration/templates/user_registration/mail_templates/confirmation_mail.html,sha256=5UkpGUrDAazrr_gKguOnOykr77a2FLgD2gnvUxzHfyg,192
357
373
  accrete/migrations/0001_initial.py,sha256=azThbc8otEhxJwo8BIgOt5eC30mxXhKJLBAazZFe3BA,4166
358
374
  accrete/migrations/0002_initial.py,sha256=dFOM7kdHlx7pVAh8cTDlZMtciN4O9Z547HAzEKnygZE,1628
375
+ accrete/migrations/0003_remove_member_name.py,sha256=bnZrzOIXcqsoGfbqgohTN5OHm2IldnLlBz1HNJDeqKc,315
359
376
  accrete/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
360
377
  accrete/utils/__init__.py,sha256=saw9zi2XItJOPbv4fjTXOpl7StNtC803jHhapFcGx08,312
361
378
  accrete/utils/dates.py,sha256=apM6kt6JhGrKgoT0jfav1W-8AUVTxNc9xt3fJQ2n0JI,1492
@@ -363,7 +380,7 @@ accrete/utils/forms.py,sha256=IvxbXNpSd4a-JBgsTJhs2GHe-DCRWX-xnVPRcoiCzbI,3104
363
380
  accrete/utils/log.py,sha256=BH0MBDweAjx30wGBO4F3sFhbgkSoEs7T1lLLjlYZNnA,407
364
381
  accrete/utils/models.py,sha256=2xTacvcpmDK_Bp4rAK7JdVLf8HU009LYNJ6eSpMgYZI,1014
365
382
  accrete/utils/views.py,sha256=AutijWetWGgjdO1PNc4gxCblT-i1fAfldNDFRbO9Sac,5012
366
- accrete-0.0.112.dist-info/METADATA,sha256=KdP41pM_6cq8Jx0oEy3TCHNTVwEhKSVI2pIivZr89Nk,4953
367
- accrete-0.0.112.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
368
- accrete-0.0.112.dist-info/licenses/LICENSE,sha256=_7laeMIHnsd3Y2vJEXDYXq_PEXxIcjgJsGt8UIKTRWc,1057
369
- accrete-0.0.112.dist-info/RECORD,,
383
+ accrete-0.0.113.dist-info/METADATA,sha256=kvqYem5bYx1j0oWAFyHESNnmukLdsBxfiu-kc-PDB-k,4953
384
+ accrete-0.0.113.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
385
+ accrete-0.0.113.dist-info/licenses/LICENSE,sha256=_7laeMIHnsd3Y2vJEXDYXq_PEXxIcjgJsGt8UIKTRWc,1057
386
+ accrete-0.0.113.dist-info/RECORD,,