save() saves all fields except ManyToMany field - python

I have a model "Contest" with one m2m field called "teams" which is related to a model "Team".
I overrided the method save. In my save() function (the one that's overriding), I need a queryset (in my save overrinding function) with all the objects related to my team m2m field. The code is self.teams.all() but it won't work because my models is not yet registered in database right ? So I call super().save(*args, **kwargs) now my models is saved and I can get my queryset ?
I can't. The queryset is empty, even if I registered team(s). <QuerySet []>
Why does super.save() save immediately all the fields except the m2m ?
I use exclusively the django admin web site to create models. No manage.py shell or form.
My model :
class Contest(models.Model):
name = models.CharField(max_length=16, primary_key=True, unique=True, default="InactiveContest", blank=True) # Ex: PSGvMNC_09/2017
id = models.IntegerField(default=1)
teams = models.ManyToManyField(Team, verbose_name="opposants")
date = models.DateTimeField(blank=True)
winner = models.ForeignKey(Team, verbose_name='gagnant', related_name='winner', on_delete=models.SET_NULL, blank=True, null=True)
loser = models.ForeignKey(Team, verbose_name='perdant', related_name='loser', on_delete=models.SET_NULL, blank=True, null=True)
bet = models.IntegerField(verbose_name='Nombre de paris', default=0, blank=True, null=0)
active = models.BooleanField(default=False)
def save(self, *args, **kwargs):
if self._state.adding:
self.active = False
# Django's id field immitation
last_id = Contest.objects.all().aggregate(Max('id')).get('id__max')
if last_id is not None:
self.id = last_id + 1
super().save(*args, **kwargs)
print(Contest.objects.get(id=self.id)) # Works --> model saved in db... in theory
queryset = self.teams.all() # Empty all the time !
Once save() (the overrinding one) has been executed one time, the problem is solved and next times (modifications) I can get my queryset so I could just use self._state.adding but this method obliges me to save 2 times (creation, editing).
I need to understand why super().save(*args, **kwargs)behaves like this and how can I solve this ?

The reason is that implementation of ManyToManyField does not use the database table of the model. It uses third table for connection between two models.
Each model has its own database table. In your example
model Contest -> table app_contest
model Team -> table app_team
However, app_contest does not have field teams in it. And app_team does not contain field contest.
Instead, there is a third table e.g. called app_contest_team that consists of three columns:
id
contest_id
team_id
And stores pairs of contest_id + team_id that represent connection between contests and teams.
So queryset = self.teams.all() is equal to the SQL expression:
SELECT * FROM app_teams WHERE id in
(SELECT team_id from app_contest_teams WHERE contest_id = '{self.id}');
Thus, you can see that you cannot anyhow manipulate ManyToManyField without having an instance initially saved in database. Because only after inserting into table app_contest it receives unique ID which can be later used for creating entries in the third table app_contest_teams.
And also because of that - save() is not required when you add new object to ManyToManyField because table of the model is not affected

Related

How to append value to the ArrayField in Django with PostgreSQL?

I have created a model with the ArrayField in Django Model. I am using PostgreSQL for the database.
I want to create new rows in the database or update the existing rows.
But I can not insert or append data to the ArrayField.
Model
class AttendanceModel(BaseModel):
"""
Model to store employee's attendance data.
"""
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='attendance_user')
date = models.DateField()
time = ArrayField(models.TimeField(), blank=True, null=True)
class Meta:
unique_together = [['user', 'date']]
def __str__(self):
return f'{self.user.username} - {self.date}'
View
attend, created = models.AttendanceModel.objects.get_or_create(user=fingerprint.user, date=attendance_date)
attend.time = attend.time.append(attendance.timestamp.time())
attend.save()
The time field does not being created or updated with the new value.
How can I do this?
attend.time = attend.time.append(attendance.timestamp.time())
Here the return value of list.append(...) is being assigned to attend.time, which is None, thus the attend.time is not getting updated.
Try this instead,
attend, created = models.AttendanceModel.objects.get_or_create(
user=fingerprint.user, date=attendance_date
)
attend.time.append(attendance.timestamp.time())
attend.save()
Following this Answer I did this.
from django.db.models import Func, F, Value
.
.
.
attend, created = models.AttendanceModel.objects.get_or_create(user=fingerprint.user, date=attendance_date)
attend.time = Func(F('time'), Value(attendance.timestamp.time()), function='array_append')
attend.save()

Set unique primary key based on foreignkey

I have a model defined as -
class sales_order(models.Model):
customer=models.ForeignKey()
item=models.ForeignKey()
branch=models.ForeignKey()
---
---other fields
Now for each branch, I want to start the primary key from 1 ("id" for eg.), but the default functionality of Django will increment the id irrespective of any other data.
I'm ok even if id keeps on incrementing as it does, and then I set my own field making it unique per branch and this field should auto increment without the user passing the data by checking the previous value from the database such as -
class order_serializer(serializers.ModelSerializer):
class Meta:
validators = [
UniqueTogetherValidator(
queryset=sales_order.objects.all(),
fields=['myOwnDefinedField', 'branch']
)
]
I'm in a state of no idea how to achieve this. Using Django 3.1.5.
Any help?
In the model's save method you can perform a query to get the greatest value in the field for the current branch, add 1 to this value and then save that as the new value. Only do this if there is not already a value so that we don't overwrite existing rows
Use Meta.unique_together to enforce this constraint at the DB level too
from django.db.models.functions import Coalesce
class SalesOrder(models.Model):
branch = models.ForeignKey(Branch, on_delete=models.CASCADE)
branch_unique_id = models.IntegerField(editable=False, blank=True)
class Meta:
unique_together = (
('branch', 'branch_unique_id'),
)
def save(self, *args, **kwargs):
if not self.branch_unique_id:
self.branch_unique_id = SalesOrder.objects.filter(
branch=self.branch
).aggregate(
max=Coalesce(models.Max('branch_unique_id'), 0)
)['max'] + 1
super().save(*args, **kwargs)

How to update Django model's pre-existing record during creation of a new one

In Django, is there a way to update an existing record while creating a new one with FK relationship to the same parent object.
To elaborate, the scenario will be:
Parent model A has a child model B.
New records are added in model B for each instance of A
On Save, before the new record is added in B, a Boolean field in the previous record is set to True.
The update of the older record will happen along with creation of new record.
If one were to fall back on legacy VB workflow, a dataset will have
been created for all values of B for an instance (PK) of A --> Sort (asc) the dataset on PK of B --> Goto the last
record of the dataset --> Change the boolean value of the hit --> Add a new record.
How does one approach this scenario in Django?
Edit
FREIGHT TABLES:
class Freight(models.Model):
frt_doc_number = models.IntegerField(null=True, blank=True, verbose_name='Frt Doc Num')
frt_doc_type = models.CharField(max_length=2, null=True, default='RF', ...)
create_date = models.DateField(default=timezone.now, ...)
mat_group = models.ForeignKey(MaterialGroup, on_delete=models.CASCADE, ...)
RecvPlant = models.ForeignKey(Plant, related_name='receiving_plant', on_delete=models.CASCADE, ...)
DispPlant = models.ForeignKey(Plant, related_name='dispatching_plant', on_delete=models.CASCADE,...)
conversion_factor = models.DecimalField(max_digits=7, decimal_places=4, ...)
class FreightItems(models.Model):
freight_item = models.AutoField(primary_key=True, verbose_name='Line Item Number')
freight_item_header = models.ForeignKey(Freight, related_name='frt_docs', on_delete=models.CASCADE, ...)
freight_item_num = models.CharField(max_length=3, default=10, null=True, ...)
from_date = models.DateField(null=True, verbose_name='From date')
to_date = models.DateField(null=True, verbose_name='To date')
freight_rate = models.DecimalField(max_digits=7, decimal_places=2, null=True, blank=True, default=0.00, ...)
frt_rate_past = models.BooleanField(default=False, verbose_name='Prev.Rate')
Logic for creating records
A freight document (header) can be created for combination of these three fields: 1. mat_group, 2. RecvPlant, 3. DispPlant.
There will always be one document for each such combination (of three fields as stated above). The rates are added to the table "FreightItems" which can change from time-to-time (generally every 15 days). So when a new rate is added (basically in update mode of the particular freight document), the last valid rate must be made "invalid" by making boolean field frt_rate_past set to True.
Creation (adding) of a new rate MUST happen simultaneously with setting the old rate "invalid" (boolean field frt_rate_past set to True), in order to ensure that old rates are not picked up.
views.py
class FreightChangeView(UpdateView):
template_name = "freight_change.html"
model = Freight
form_class = FreightForm
success_url = reverse_lazy('freights_list')
def clean_status(self):
return self.instance.status
def get_context_data(self, **kwargs):
data = super(FreightChangeView, self).get_context_data(**kwargs)
if self.request.POST:
data['freightitems'] = UpdateFreightFormset(self.request.POST, self.request.FILES)
else:
data['freightitems'] = UpdateFreightFormset()
return data
def form_valid(self, form):
context = self.get_context_data()
freightitems = context['freightitems']
with transaction.atomic():
self.object = form.save()
if freightitems.is_valid():
self.object = form.save()
freightitems.instance = self.object
freightitems.save()
# return redirect('freights_list')
else:
context.update({'freightitems': freightitems})
return self.render_to_response(context)
return super(FreightChangeView, self).form_valid(form)
You can't do it in one query, but you can do it in a transaction.
from django.db import transaction
def viewfunc(request):
with transaction.atomic():
# Before the new records are added in B, a Boolean field is set to True.
# New records are added in model B for each instance of A

Significant performance issue with Django Admin - foreign key labels

I’m experience significant performance issue with Django Admin.
I have a mapping model where I map primary keys of 2 other modes
In my FundManagerMappingAdmin I try to represent the foreign key of the 2 tables with a label from the foreign key models.
The underlying models are about 4000 lines
I’m experiencing slow performance when retrieving the list in admin and then also when editing and updating
Could someone please point out the inefficiencies in this code?
Is there a better way please?
Admin.py
#admin.register(ChampDwDimFundManagerMapping)
class FundManagerMappingAdmin(admin.ModelAdmin):
list_display = ['get_champ_fund_manager_id', 'get_evestment_name', 'get_sharepoint_name', ]
def get_champ_fund_manager_id(self, obj):
return obj.fund_manager_id
get_champ_fund_manager_id.short_description = 'CHAMP Manager ID'
def get_evestment_name(self, obj):
return obj.evestment_fund_manager_id.manager_name
get_evestment_name.short_description = 'Evestment Manager Name'
def get_sharepoint_name(self, obj):
return obj.sharepoint_fund_manager_id.manager_name
get_sharepoint_name.short_description = 'Sharepoint Manager Name'
def get_form(self, request, obj=None, **kwargs):
form = super(ChampFundManagerMappingAdmin, self).get_form(request, obj, **kwargs)
form.base_fields['sharepoint_fund_manager_id'].label_from_instance = lambda obj: "{} {}".format(obj.final_publications_fund_manager_id, obj.manager_name)
form.base_fields['evestment_fund_manager_id'].label_from_instance = lambda obj: "{} {}".format(obj.evestment_fundmanager_id_bk, obj.manager_name)
return form
Models.py
class FundManagerMapping(models.Model):
fund_manager_id = models.AutoField(db_column='FundManagerId', primary_key=True)
sharepoint_fund_manager_id = models.ForeignKey(SharePointFundManager, models.DO_NOTHING, db_column='SharePointFundManagerId')
evestment_fund_manager_id = models.ForeignKey(EvestmentFundManager, models.DO_NOTHING, db_column='eVestmentFundManagerId')
class EvestmentFundManager(models.Model):
evestment_fund_manager_id = models.AutoField(db_column='eVestmentFundManagerId', primary_key=True)
package_execution_id = models.IntegerField(db_column='PackageExecutionId')
evestment_fund_manager_id_bk = models.CharField(db_column='eVestmentFundManagerId_BK', max_length=50)
manager_name = models.CharField(db_column='ManagerName', max_length=255)
class SharePointFundManager(models.Model):
sharepoint_fund_manager_id = models.AutoField(db_column='SharePointFundManagerId', primary_key=True)
package_execution_id = models.IntegerField(db_column='PackageExecutionId')
research_fund_manager_id = models.CharField(db_column='ResearchFundManagerId', max_length=50, blank=True, null=True)
final_publications_fund_manager_id = models.CharField(db_column='FinalPublicationsFundManagerId', max_length=50, blank=True, null=True)
manager_name = models.CharField(db_column='ManagerName', max_length=255)
You are showing the name of related entities (because of get_evestment_name and get_sharepoint_name) without joining/prefetching them. That means for every row that you display and every name of the related entity it requires django to make a database query. You need to override get_queryset() of the ModelAdmin and use select_related to tell django to join those entities from the beginning so that it does not need any additional queries to get those names:
#admin.register(ChampDwDimFundManagerMapping)
class FundManagerMappingAdmin(admin.ModelAdmin):
def get_queryset(self, request):
return super().get_queryset(request).select_related(
'sharepoint_fund_manager_id',
'evestment_fund_manager_id',
)
Also you don't name ForeignKey fields something_id. It is just sharepoint_fund_manager because what you get when you call fund_manager.sharepoint_fund_manager_id is not an id but an instance of SharePointFundManager. It is weird to call sharepoint_fund_manager_id.name. An id does not have a name attribute. A fund manager has.
Additionally Django does automatically create a property sharepoint_fund_manager_id for you if you call the field sharepoint_fund_manager to access the plain foreign key.

Finding type of model's field in django

I have a model like this:
class Profile(models.Model):
user = models.OneToOneField("User", verbose_name=u"user", null=True, blank=True, default=None)
monthly_income = models.ForeignKey(MonthlyIncome, verbose_name=u"Monthly Income", null=True, blank=True, default=None)
car = models.ManyToManyField(Car, verbose_name=u"Car?", null=True, blank=True, default=None)
If user changes his profile,these changes saved to another table(UpdateLog). In view I make a query from UpdateLog and if item is in Updatelog, select box would be disable. Because disabled selectboxes doesn't send any data to server, I'm adding the disabled field's value withrequest.POST.copy() as follow view:
in view:
if request.method == "POST":
post_values = request.POST.copy()
query = UpdateLog.objects.filter(user=request.user.id)
for a in query:
field = a.filed_name
try:
sd = eval("profile.%s.id"%field)
post_values.update({field: u"%s"%sd})
except AttributeError:
print field
The problem is: sd = eval("profile.%s.id"%field) is only works for ForeignKey data. I have to separate sd for foreignkey and manytomany fields. How can I recognize type of model's fields in view?
I think you're looking for something like this:
from django.db import models
field_name = a.field_name
field = getattr(profile, field_name)
if isinstance(field, models.Model):
# It's a model object
# (accessing a foreign key returns a model object)
elif isinstance(field, models.manager.Manager):
# It's a manager object
# (accessing a many to many field returns a ManyRelatedManager)
else:
raise ValueError("Unexpected field type")
Consider looking at Django's model options (eg, the Meta class) for a more general solution.
from django.db import models
model_fields = dict((f.name, f) for f in MyModel._meta.fields)
test_field = field_dict[field_name]
if isinstance(test_field, models.OneToOneField):
#it's a one to one field!
elif isinstance(test_field, models.ManyToManyField):
#it's a one to one field!
elif isinstance(test_field, models.IntegerField):
#it's an integer field!
#...
One of the benefits of this approach, outside of generality, is that it doesn't require any database access- it just inspects the schema specified by your model definitions.

Categories

Resources