Reputation: 37
I have a peculiar type of model where table_fields
needs to set to default depending on the value of its report_type
field.
class Report(models.Model):
ALL_TABLE_FIELDS = (
'local_ordered_date', 'card', 'country', 'customer', 'supplier',
'amount', 'currency', 'quantity', 'quantity_unit', 'invoice_total',
'payment_total', 'balance', 'owner', 'lost_reason', 'lost_description', 'result',
'local_result_updated_at', 'local_delivery_deadline', 'board', 'stage', 'order_number',
'product', 'incoterms', 'destination_port', 'loading_port', 'payment_terms',
'other_terms'
)
DEFAULT_TABLE_FIELDS = {
'OrderTrendReport': ('card', 'board', 'customer', 'amount',
'currency', 'stage', 'owner', 'country',
'local_ordered_date', 'local_delivery_deadline',
'order_number'),
'RevenueTrendReport': ('card', 'customer', 'invoice_total',
'payment_total', 'balance', 'currency',
'owner', 'order_number', 'board'),
'DealSuccessRateReport': ('card', 'board', 'customer', 'amount',
'currency', 'owner', 'result',
'local_result_updated_at'),
'DealLostReasonReport': ('card', 'board', 'customer', 'amount',
'currency', 'owner',
'local_result_updated_at', 'lost_reason',
'lost_description'),
'OrderByCategoryReport': ('card', 'board', 'customer', 'amount',
'currency', 'stage', 'owner', 'country',
'local_ordered_date', 'local_delivery_deadline',
'order_number')
}
"""
The proper way to set a field's default value to a function call/callable
is to declare a function before the field and use it as a callable in default_value named arg
https://stackoverflow.com/questions/12649659/how-to-set-a-django-model-fields-default-value-to-a-function-call-callable-e
"""
class ReportType(models.TextChoices):
# hard-coded in order to avoid ImportError and AppRegistryNotReady
OrderTrendReport = 'OrderTrendReport', 'Order Trend report'
RevenueTrendReport = 'RevenueTrendReport', 'Revenue Trend report'
DealSuccessRateReport = 'DealSuccessRateReport', 'Deal Success Rate report'
DealLostReasonReport = 'DealLostReasonReport', 'Deal Lost Reason report'
OrderByCategoryReport = 'OrderByCategoryReport', 'Order By Category report'
user = models.ForeignKey(User, on_delete=models.CASCADE)
report_type = models.CharField(choices=ReportType.choices,
max_length=50,
default=ReportType.OrderTrendReport)
table_fields = ArrayField(models.CharField(max_length=50),
default=list)
@property
def all_table_fields(self):
return self.ALL_TABLE_FIELDS
def save(self, *args, **kwargs):
if not self.pk: # this will ensure that the object is new
self.table_fields = self.DEFAULT_TABLE_FIELDS[self.report_type]
super().save(*args, **kwargs)
After finishing the code for models.py
, I went on and made a migration file. Then I edited this like so:
import django.contrib.postgres.fields
from django.db import migrations, models
import django.db.models.deletion
def make_report_fields(apps, schema_editor):
User = apps.get_model(settings.AUTH_USER_MODEL)
Report = apps.get_model('deal', 'Report')
ReportTypes = ('OrderTrendReport', 'RevenueTrendReport', 'DealSuccessRateReport',
'DealLostReasonReport', 'OrderByCategoryReport')
report_fields = [Report(user=user, report_type=report_type)
for report_type in ReportTypes
for user in User.objects.all()]
Report.objects.bulk_create(report_fields)
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('deal', '0059_auto_20210610_0948'),
]
operations = [
migrations.CreateModel(
name='Report',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('report_type', models.CharField(choices=[('OrderTrendReport', 'Order Trend report'), ('RevenueTrendReport', 'Revenue Trend report'), ('DealSuccessRateReport', 'Deal Success Rate report'), ('DealLostReasonReport', 'Deal Lost Reason report'), ('OrderByCategoryReport', 'Order By Category report')], default='OrderTrendReport', max_length=50)),
('table_fields', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=50), default=list, size=None)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.RunPython(make_report_fields)
]
When I ran python migrate deal {# of migration file}
, the table was made, with many rows corresponding to the existing users.
However, the table_fields
were all empty.
How do I fix it so that the table_fields
will be set to correct default according to its 'report_type'?
Upvotes: 0
Views: 278
Reputation: 21812
As described in the documentation one of the caveats of bulk_create
is as follows:
The model’s
save()
method will not be called, and thepre_save
andpost_save
signals will not be sent.
Also custom model methods are not available in the migrations as they use a serialized representation of your models without them. Hence your code never uses the save
method causing your code to not work as you want it to. Instead of relying on the save
method why don't you simply pass the correct array yourself? Something like follows:
DEFAULT_TABLE_FIELDS = {
'OrderTrendReport': ('card', 'board', 'customer', 'amount',
'currency', 'stage', 'owner', 'country',
'local_ordered_date', 'local_delivery_deadline',
'order_number'),
'RevenueTrendReport': ('card', 'customer', 'invoice_total',
'payment_total', 'balance', 'currency',
'owner', 'order_number', 'board'),
'DealSuccessRateReport': ('card', 'board', 'customer', 'amount',
'currency', 'owner', 'result',
'local_result_updated_at'),
'DealLostReasonReport': ('card', 'board', 'customer', 'amount',
'currency', 'owner',
'local_result_updated_at', 'lost_reason',
'lost_description'),
'OrderByCategoryReport': ('card', 'board', 'customer', 'amount',
'currency', 'stage', 'owner', 'country',
'local_ordered_date', 'local_delivery_deadline',
'order_number')
}
def make_report_fields(apps, schema_editor):
User = apps.get_model(settings.AUTH_USER_MODEL)
Report = apps.get_model('deal', 'Report')
ReportTypes = ('OrderTrendReport', 'RevenueTrendReport', 'DealSuccessRateReport',
'DealLostReasonReport', 'OrderByCategoryReport')
report_fields = [Report(user=user, report_type=report_type, table_fields=DEFAULT_TABLE_FIELDS[report_type])
for report_type in ReportTypes
for user in User.objects.all()]
Report.objects.bulk_create(report_fields)
Upvotes: 1