I want to update m2m field on save() method
I have the following model:
class Tag(models.Model):
label = models.CharField(max_length=50)
parents_direct = models.ManyToManyField("Tag", related_name="children", blank=True)
created = models.DateTimeField(auto_now_add=True)
description = models.TextField(null=True, blank=True)
administrators = models.ManyToManyField(
to=KittyUser, related_name="administrated_tags", blank=True)
moderators = models.ManyToManyField(
to=KittyUser, related_name="moderated_tags", blank=True)
allowed_users = models.ManyToManyField(
to=KittyUser, related_name="allowed_tags", blank=True)
visible = models.BooleanField(default=True, verbose_name="visible to anyone")
POSTABILITY_CHOICES = (
('0', 'any allowed user can post'),
('1', 'only moderators\\admins can post')
)
postability_type = models.CharField(default='0',
max_length=1,
choices=POSTABILITY_CHOICES)
parents_tree = models.ManyToManyField("Tag", related_name="parents_tree_in_for", blank=True)
related_tags = models.ManyToManyField("Tag", related_name="related_tags_in_for", blank=True)
def save(self, *args, **kwargs):
self.label="overriden label"
super(Tag, self).save(*args, **kwargs)
self.parents_tree.add(*self.parents_direct.all())
breakpoint()
super(Tag, self).save(*args, **kwargs)
Through django-admin the tags get created, the label substitution works, though parents_tree don't get updated.
If I create it from the shell, it is swearing at the second super().save:
django.db.utils.IntegrityError: duplicate key value violates unique constraint "posts_tag_pkey"
If you take away the first super.save(), you get the following:
"<Tag: overriden label>" needs to have a value for field "id" before this many-to-many relationship can be used.
in both shell and admin.
The question is, how to update my m2m field on save?
Another question is why does it work differently in admin panel and shell?
As a temporary solution I managed to listen to the signal of updating parents_direct field, but what if I wanted to depend on non-m2m fields?
from django.db.models.signals import m2m_changed
def tag_set_parents_tree(sender, **kwargs):
if kwargs['action'] == 'post_add' or 'post_remove':
parents_direct = kwargs['instance'].parents_direct.all()
if parents_direct:
kwargs['instance'].parents_tree.set(parents_direct)
for tag in parents_direct:
kwargs['instance'].parents_tree.add(*tag.parents_tree.all())
else:
kwargs['instance'].parents_tree.clear()
m2m_changed.connect(tag_set_parents_tree, sender=Tag.parents_direct.through)
Related
I am trying to override the save method in a model with logic to update a couple of many to many fields. Using print statements I can see values updating as expected but the values are not persisted after save.
In the below model the change_access_flag is changing as expected with a signal, the prints are executing with the appropriate values, but the allowed_departments and allowed_communities fields are not updating with the printed values.
Model
class Person(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
full_name = models.CharField(null=True, blank=True, max_length=50)
payroll_id = models.CharField(null=True, max_length=20)
position = models.ForeignKey(Position, null=True, on_delete=models.SET_NULL)
primary_community = models.ForeignKey(Community, null=True, on_delete=models.CASCADE, related_name="primary_community")
region = models.CharField(max_length=2, choices=RegionChoices.choices, blank=True, null=True)
allowed_communities = models.ManyToManyField(Community, blank=True, related_name="allowed_community")
allowed_departments = models.ManyToManyField(Department, blank=True)
access_change_flag = models.BooleanField(default=False)
def __str__(self):
return f'{self.user.first_name} {self.user.last_name}'
class Meta:
verbose_name_plural = "People"
ordering = ['position__position_code', 'user__last_name', 'full_name']
def save(self, *args, **kwargs):
#Set Full Name field
if self.user.last_name:
self.full_name = f'{self.user.first_name} {self.user.last_name}'
super().save(*args, **kwargs)
#Change flag set in signals, set for events that require updating access settings
if self.access_change_flag:
self.access_change_flag = False
#Allowed community access
access_level = self.position.location_access_level
self.allowed_communities.clear()
if access_level == 'R':
if self.primary_community.community_name == '#':
region = self.region
else:
region = self.primary_community.region
if region is not None:
communities = Community.objects.filter(region=region)
self.allowed_communities.set(communities)
self.allowed_communities.add(self.primary_community)
elif access_level == 'A':
communities = Community.objects.filter(active=True)
self.allowed_communities.set(communities)
else:
communities = self.primary_community
self.allowed_communities.add(communities)
print(self.allowed_communities.all())
#Allowed department access
dept_access = self.position.department_only_access
if dept_access:
depts = [self.position.department]
else:
depts = Department.objects.filter(active=True)
self.allowed_departments.set(depts)
print(self.allowed_departments.all())
super().save(*args, **kwargs)
I have tried variations of set, clear, add, moving the super.save() around, and placing the logic in a signal but nothing seems to work. I have tested initiating save from both a model form through a view and admin.
Let me answer in quotes. You can find the source in this section.
If you wish to update a field value in the save() method, you may also
want to have this field added to the update_fields keyword argument.
This will ensure the field is saved when update_fields is specified.
Also read here
Specifying update_fields will force an update.
So try to call the super().save(*args, **kwargs) method at the end with defining the argument update_fields. This will force the update of your model regarding the specified fields.
Let me know how it goes.
We are using 'actstream' library and its not updating the actual many2many field id values into history table. Always its updating as an empty list instead of list of ids.
class Parent():
name = models.CharField(max_length=255)
tags = TaggableManager(blank=True)
def __str__(self):
return self.name
class Table1():
name = models.CharField(max_length=255, null=True)
type = models.CharField(max_length=255, null=True)
parent_id = models.ManyToManyField(ParentTable, blank=True, related_name='%(class)s_parent_id')
tags = TaggableManager(blank=True)
def __str__(self):
return self.name
'id' is auto incremented value in Django table. Once we call a save() method, then post_save signal will execute for logging additional information in the actstream table.tags and parent_id is updating as [] instead of user sending values in the actstream_action table.we are using #receiver(post_save) annotation and executing action.send() accordingly
#receiver(post_save)
def capture_models_post_save(sender, instance, created, **kwargs):
userInfo = get_current_user()
action.send(userInfo, verb='created',description='created',action_object=instance, modification=model_to_dict(instance))
I developed a Django Application and it was working correctly, until I did a data migration to the database created by django migration, I migrated the data using an sql script and Pgadmin.
Now I have the database full with records but when I am trying to add new record using django form I got the below error:
duplicate key value violates unique constraint > "learningcenters_partnerorganization_pkey"
DETAIL: Key (id)=(1) already exists.
taking into consideration that the available id for this table is 10.
Model:
class SLPAcademicRound(models.Model):
name = models.CharField(max_length=45,
unique=True,
verbose_name=_('Academic Round Name'))
code = models.CharField(max_length=5,
unique=True,
verbose_name=_('Code'))
cycle = models.ForeignKey(
SLPCycle,
blank=False, null=True,
verbose_name=_('SLP Cycle/SLP Cycle'),
on_delete=models.CASCADE,
)
learning_center = models.ForeignKey(
LearningCenter,
blank=False, null=True,
verbose_name=_('Learning Center'),
on_delete=models.CASCADE,
)
round_date_start = models.DateField(
blank=True,
null=True,
verbose_name=_('SLP Round Start Date')
)
round_date_end = models.DateField(
blank=True,
null=True,
verbose_name=_('SLP Round End Date')
)
current_round = models.BooleanField(
blank=True,
null=True,
verbose_name=_('Current Round')
)
View:
class AddSLPAcademicRoundView(LoginRequiredMixin,
GroupRequiredMixin,
CreateView):
template_name = 'bootstrap4/common_form.html'
form_class = SLPAcademicRoundForm
queryset= SLPAcademicRound.objects.all()
group_required = ["LearningCenterManager"]
def get_absolute_url(self):
return reverse("slp:slp_academic_round_list")
def form_valid(self, form):
print((form.cleaned_data))
form.save(self.request)
return super(AddSLPAcademicRoundView, self).form_valid(form)
def get_form_kwargs(self, *args, **kwargs):
kwargs = super().get_form_kwargs(*args, **kwargs)
kwargs['user'] = self.request.user
return kwargs
I found the solution by pg_get_serial_sequence can be used to avoid any incorrect assumptions about the sequence. This resets the sequence in one shot:
SELECT pg_catalog.setval(pg_get_serial_sequence('table_name', 'id'), (SELECT MAX(id) FROM table_name)+1);
Run the command
manage.py sqlsequencereset
This will give you a script to reset the table ids to normal.
Thereafter, run the script in psql and all the table's sequences will be reset.
Hi I have the following django model:
class Issue(models.Model):
title = models.CharField(max_length=200)
date = models.DateTimeField(auto_now=True)
assignee = models.ForeignKey(User, on_delete=models.CASCADE, related_name='assignee')
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='owner', null=True, blank=True)
description = models.TextField()
state = models.IntegerField(choices=STATUS_CHOICES, default=1)
priority = models.IntegerField(choices=RELEVANCE_CHOICES, default=2)
expired_date = models.DateField(auto_now=False, null=True, blank=True)
and a form which allow a user to create an Issue instance:
class IssueForm(forms.ModelForm):
class Meta:
model = Issue
fields = ('title', 'description', 'assignee', 'state', 'priority', 'expired_date')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['title'].label = "Titolo"
self.fields['description'].label = "Descrizione"
self.fields['state'].label = "Stato"
self.fields['priority'].label = "Priorità"
self.fields['expired_date'].label = "Termine"
self.fields['expired_date'].widget.attrs.update({'class': 'datepicker'})
self.fields['assignee'] = forms.MultipleChoiceField(
choices=self.fields['assignee'].choices,
widget=forms.CheckboxSelectMultiple,
label=("Assegnatario")
)
def clean(self):
cleaned_data = super().clean()
user_id = [i for i in cleaned_data['assignee']]
cleaned_data['assignee'] = [User.objects.get(id=i) for i in user_id]
return cleaned_data
I render this form and the field assignee is a checkbox.
I would like to be able to choose several assignee for the same issue, but I got an error because the Issue model expect just one User instance
How can I modify my model Issue in order to get more than one user ?
Thanks
you can create a new class and name it Issue_Instance where every Issue Object can have an assignee as a foreign key the problem that the relation is one to many because you have to choose more than one assignee and Django doesn't support the idea of having Array or List of Foreign Keys(I don't know any frame works that do :=) ) so I would suggest creating a new class or make the foreign key relation one-to-many key field read about it it will be very useful to solve your problem
I am trying to save a form which have ForeignKey (purchaseContractID).Here is my contract Model
class contracts(models.Model):
productDetailID=models.ForeignKey('Inventory.productDetails',related_name='+',on_delete=models.CASCADE,verbose_name='Select Product',default=None)
supplierID=models.ForeignKey(suppliers,on_delete=models.CASCADE,verbose_name='Select Supplier',default=None)
totalUnits=models.IntegerField(verbose_name='Total Units',editable=False,default=None)
ratePerUnit=models.IntegerField(verbose_name='Rate Per Unit',default=None)
saleTax=models.IntegerField(verbose_name='Sale Tax',default=None)
incomeTax=models.IntegerField(verbose_name='Income Tax',default=None)
saleTaxwithHeld=models.IntegerField(verbose_name='Sale Tax with Held',default=None)
startDate=models.DateField(verbose_name='Start Date',default=None)
endDate=models.DateField(verbose_name='End Date',default=None)
manulContractNumber=models.IntegerField(verbose_name='Manul Contract Number',default=None)
paymentDays=models.IntegerField(verbose_name='Payment Days',default=None)
remarks=models.CharField(verbose_name='Remarks',max_length=100,default=None)
dateOfEntry=models.DateField(editable=False,default=datetime.now())
def __str__(self):
return str(self.productDetailID.name)
here is my inventoryIn Model which foreignKey of PurchaseContract
class inventoryIn(models.Model):
supplierID=models.ForeignKey('Purchase.suppliers',editable=False,on_delete=models.CASCADE,verbose_name='Supplier')
productID=models.ForeignKey(products,editable=False,on_delete=models.CASCADE)
purchaseContractID=models.ForeignKey('Purchase.contracts',on_delete=models.CASCADE,verbose_name='Contract ID')
unitsIn=models.IntegerField(verbose_name='Enter No of Bags')
MYCHOCIES = (('orginal', 'ORGINAL'), ('dummy', 'DUMMY'))
doType = models.CharField(blank=True, choices=MYCHOCIES, verbose_name='Select DO Type', max_length=20)
doID=models.IntegerField(verbose_name='Do No')
doImage=models.ImageField(upload_to='doImage/%Y/%m/%d',verbose_name='Do Image')
invoiceID=models.IntegerField(verbose_name='Invoice No')
invoiceImage=models.ImageField(upload_to='inventoryIn/%Y/%m/%d')
agingDate=models.DateField(verbose_name='Receiving Date')
labReportImage = models.ImageField(upload_to='labReportImage/%Y/%m/%d', blank=True,verbose_name='Lab Report Image')
enterPaymentDays = models.IntegerField(verbose_name='Enter Payment Days', blank=True, default=None)
dateOfEntry=models.DateField(default=datetime.now())
def __str__(self):
return self.supplierID
here is my admin.py where i am adding admin form and also adding a js that add some fields dynamical.
class inventoryInAdmin(admin.ModelAdmin):
fields = ['purchaseContractID','unitsIn','doType','doID','doImage','invoiceID','invoiceImage','agingDate','labReportImage','enterPaymentDays']
class Media:
js = ('js/addInventory.js',)
admin.site.register(inventoryIn,inventoryInAdmin)
it is not allowing me to submit form and giving me error "Select a valid choice. That choice is not one of the available choices."
I have resolved this issue by adding the save method at InventoryIn Model.
class inventoryIn(models.Model):
supplierID=models.ForeignKey('Purchase.suppliers',editable=False,on_delete=models.CASCADE,verbose_name='Supplier')
productID=models.ForeignKey(products,editable=False,on_delete=models.CASCADE)
purchaseContractID=models.ForeignKey('Purchase.contracts',on_delete=models.CASCADE,verbose_name='Contract ID')
unitsIn=models.IntegerField(verbose_name='Enter No of Bags')
MYCHOCIES = (('orginal', 'ORGINAL'), ('dummy', 'DUMMY'))
doType = models.CharField(blank=True, choices=MYCHOCIES, verbose_name='Select DO Type', max_length=20)
doID=models.IntegerField(verbose_name='Do No')
doImage=models.ImageField(upload_to='doImage/%Y/%m/%d',verbose_name='Do Image')
invoiceID=models.IntegerField(verbose_name='Invoice No')
invoiceImage=models.ImageField(upload_to='inventoryIn/%Y/%m/%d')
agingDate=models.DateField(verbose_name='Receiving Date')
labReportImage = models.ImageField(upload_to='labReportImage/%Y/%m/%d', blank=True,verbose_name='Lab Report Image')
enterPaymentDays = models.IntegerField(verbose_name='Enter Payment Days', blank=True, default=None)
dateOfEntry=models.DateField(default=datetime.now())
def __str__(self):
return str(self.supplierID)
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
from Purchase.models import contracts,suppliers
contract = contracts.objects.values('supplierID', 'productDetailID').filter(id=self.purchaseContractID.id)
supplier=contract[0].get("supplierID")
product=contract[0].get("productDetailID")
self.supplierID=suppliers.objects.get(id=supplier)
self.productID=products.objects.get(productDetailsID=product)
super(inventoryIn,self).save()
and i also had made my two fields editable=False
supplierID=models.ForeignKey('Purchase.suppliers',editable=False,on_delete=models.CASCADE,verbose_name='Supplier')
productID=models.ForeignKey(products,editable=False,on_delete=models.CASCADE)