Django problem updating date field when updating a record - django

I have a problem when I try to update a field named date_updated. My intention with the field is that every time a record is updated, the date_updated field of that record should be updated by the date the change is made. That field and one other field I have inside a Base class and then in each of the models I inherit that class to repeat the fields.
class Base(models.Model):
...
date_updated = models.DateTimeField(auto_now=True)
def save(self, *args, **kwargs):
self.date_updated = django.timezone.now()
super(Base, self).save(*args, **kwargs)
class Meta:
abstract = True
class OtherClass(Base):
...
My intention is that when I update any record in the OtherClass, its date_updated field will be updated.
I also tried adding the overwrite of the save method in the OtherClass, but the result is the same. The date is kept after I make the change.
I am making the change with .update(**data_to_update)

I did this when i wanted to update only the updated_on (datetime) column:
This might help you:
from datetime import datetime
def get_current_datetime_str():
now = datetime.now()
return now.strftime("%Y-%m-%d %H:%M:%S")
class ModelName(models.Model):
date_updated=models.CharField(max_length=100) #whatever your field is
...
def __str__(self):
return self.name
continue and write this below def str(self):
def save(self, *args, **kwargs):
self.date_updated = get_current_datetime_str()
super().save(*args, **kwargs)

Related

Prevent m2m_changed from updating date modified of parent class

I have a class called Outfit and within the class, a many to many relationship called Products like this:
class Outfit(models.Model):
products = models.ManyToManyField(Product,
related_name='outfits',
blank=True)
created = models.DateTimeField(editable=False)
modified = models.DateTimeField()
class Meta:
def save(self, *args, **kwargs):
''' On save, update timestamps '''
if not self.id:
self.created = timezone.now()
self.modified = timezone.now()
return super(Outfit, self).save(*args, **kwargs)
I have a variable called product_outfit_count in the products model which basically tracks how many outfits a particular product is used in. When an outfit is saved, this signal is called:
m2m_changed.connect(update_product_outfit_count,
sender=Outfit.products.through)
The problem is, when a product's outfit count is updated, the date modified of the outfit changes as well. How can I prevent this?
EDIT:
update_product_count method:
def update_product_outfit_count_task(product_ids):
Product = apps.get_model('products.Product')
with transaction.atomic():
for product in Product.objects.filter(pk__in=product_ids):
product.outfit_count = product.outfits.count()
product.save()

Django Use Many To Many with Through in Form

I have a data model where I am using a manual intermediate table for a m2m relationship.
Building on the classical example from the django doc:
from django.db import models
INSTRUMENT_CHOICES = (
('guitar', 'Guitar'),
('bass', 'Bass Guitar'),
('drum', 'Drum'),
('keyboard', 'Keyboard'),
)
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Leadership')
def __str__(self):
return self.name
def get_leadership():
return self.leadership_set.first()
class Leadership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
instrument = models.CharField('Playing Instrument', choices=INSTRUMENT_CHOICES,
max_length=15,
null=True,
blank=False)
class Meta:
unique_together = ('person', 'group')
When I create a new group I also want to specify who is going to be the leader, and for this relationship also specify which instrument he will play in that group.
What really confuses me, given also the lack of documentation on this topic is how to handle this kind of relationship in forms.
This is the form I came with:
class InstrumentField(forms.ChoiceField):
def __init__(self, *args, **kwargs):
super().__init__(INSTRUMENT_CHOICES, *args, **kwargs)
class GroupForm(forms.ModelForm):
instrument = InstrumentField(required=False)
class Meta:
model = Group
fields = ['name',
'members'
'instrument'] # This works but it's not correctly initalized in case of edit form
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.pk is not None: # editing
# PROBLEM: this doesn't work
self.fields["instrument"].initial = self.instance.get_leadership().instrument
def save(self, commit=True):
group = super().save(commit=False)
if commit:
group.save()
if 'instrument' in self.changed_data:
leader = self.cleaned_data.get('members').first()
instrument = self.cleaned_data['instrument']
Leadership.objects.update_or_create(person=leader, group=group, defaults={'instrument': instrument})
return group
As suggested in the django doc I am manually instantiating Leadership objects (see the form save method).
What I couldn't solve is how to populate the instrument field in case of form editing. I try to do this in the __init__: first I check that we are in "edit" mode (the instance has a pk) then I get the relevant Leadership object (see Group.get_leadership) and from that I extract the instrument and I assign it to the fields["instrument"].initial.
This doesn't work.
I could inspect that the initial value was set but then when I render the form the default choice value is shown (the first value of the INSTRUMENT_CHOICES).
What am I missing here?
Is there a better way or a better docs on how to handle m2m with through model in forms?

Override save method for every class in models.Model

I need to get every class, that inherits models.Model to have created and updated field. I can achieve this by adding custom save method to every field,
def save(self, *args, **kwargs):
if not self.id:
self.created = timezone.now()
self.modified = timezone.now()
return super(`models.Model`, self).save(*args, **kwargs)
but this violates Don'tRepeatYourself rule.
I've tried to override models.Model:
class LogModel(models.Model):
created = models.DateTimeField(editable=False)
updated = models.DateTimeField()
def save(self, *args, **kwargs):
if not self.id:
self.created = timezone.now()
self.updated = timezone.now()
return super(LogModel, self).save(*args, **kwargs)
and use LogModel instead of models.Model, but this failed with error E006(
The field 'x' clashes with the field 'x' from model 'y.logmodel'.
EDIT
My main question is how to add a custom specific field to all models in my models.py
Your base model must be abstract:
class LogModel(models.Model):
class Meta:
abstract = True
created = models.DateTimeField(editable=False)
updated = models.DateTimeField()
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
# Use self._state.adding to check if this is a new instance,
# ID not being empty is not a guarantee that the instance
# exists in the database
# and if `update_fields` is passed, you must add the fields to the
# list or they won't be saved in the database.
if force_insert or self._state.adding:
self.created = timezone.now()
if update_fields and 'created' not in update_fields:
update_fields.append('created')
self.updated = timezone.now()
if update_fields and 'updated' not in update_fields:
update_fields.append('updated')
return super(LogModel, self).save(*args, **kwargs)
However, if you override the save() method, this means it won't be editable in any form. If this is what you want, then you are better to use auto_now and auto_now_add:
class LogModel(models.Model):
class Meta:
abstract = True
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
Instead of overriding save method, you could define auto_now_add and auto_now parameters in the Model field like:
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
For more information on these parameters, you can check the django docs.
It can be done by defining Abstract Base Model and define save method there and create all the models by inheriting from the abstract class. e.g.
class MyAbstractModel(models.Model):
created = models.DateTimeField(editable=False)
updated = models.DateTimeField()
def save(self, *args, **kwargs):
if self._state.adding:
self.created = timezone.now()
self.updated = timezone.now()
return super(LogModel, self).save(*args, **kwargs)
class Meta:
abstract = True
and create child model class from it:
class Record(MyAbstractModel):
pass
Unfortunately, using update_fields in .save() does not trigger fields with auto_now, so I overridden save method this way:
class BaseModel(models.Model):
class Meta:
abstract = True
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
_base_update_fields = ['updated_at']
def save(
self, force_insert=False, force_update=False,
using=None, update_fields=None
):
_update_fields = None
if update_fields is not None:
_update_fields = self._base_update_fields + update_fields
super().save(
force_insert=force_insert, force_update=force_update,
using=using, update_fields=_update_fields
)

Behaviour of Django's forms.ModelForm.save(): Why does models.save_instance not work via parent?

I am using class based views, but the forms have two underlying models, instead of one. So the "main" form (the Employee, the view knows about) has another form object.
The model of the Shift has a pkey, referencing one element of the Employees.
I would like to know, why this is not working:
class ShiftSubForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super( ShiftSubForm, self).__init__(*args, **kwargs)
... some adjustment of widgets, deleting and adding form fields
class Meta:
model=Shift
class EmployeeForm(forms.ModelForm):
second_form = None
def __init___(self, *args, **kwargs):
kwargs.update({'instance': kwargs['shift']})
del kwargs['shift']
self.second_form = ShiftSubForm(*args, **kwargs)
def save(self):
employee = super(ShiftSubForm, self).save()
self.second_form.cleaned_data.update({'employee': employee})
self.second_form.save()
class Meta:
model = Employee
I'd expect the save() in the Parents of ShiftSubForm to call models.save_instance(..) and to save the data. But it fails, with an integrity error because employee_id is Null. So the employee object didn't make it into the save.
But, if i call it directly, it works:
class ShiftSubForm(forms.ModelForm):
... as above ...
def save(self):
return models.save_instance(self, self.instance, self._meta.fields,
fail_message, True, exclude=self._meta.exclude)
What am i missing?
EDIT: Can't answer myself, so here ...
Think that this might be the best way?
class ShiftSubForm(forms.ModelForm):
... as above ...
def createInstanceWith(self,employee):
self.save(commit=False) # prepares self.instance
self.instance.employee = employee
class EmployeeForm(forms.ModelForm):
...
def save(self):
employee = super(ShiftSubForm, self).save()
self.second_form.full_clean() # populates its self.cleaned_data
self.second_form.createInstanceWith(employee)
self.second_form.save()
PS: Ignore typos - this is not the real code. But it has everything, that fumbles around with the forms
Don't update the cleaned_data. Call save() with commit=False, set the employee then save it to the db.
class EmployeeForm(forms.ModelForm):
second_form = None
def save(self):
employee = super(ShiftSubForm, self).save()
shift = self.second_form.save(commit=False)
shift.employee = employee
shift.save()
return employee
class Meta:
model = Employee

How can I get a Django foreignkey using a TextInput widget to display a name, rather than an id?

I have the following (simplified for this example) Django models:
class Ingredient(models.Model):
name = models.CharField(max_length=100)
def __unicode__(self):
return self.name
class RecipeIngredient(models.Model):
quantity = models.DecimalField(max_digits=5, decimal_places=3)
unit_of_measure = models.ForeignKey(UnitOfMeasure)
ingredient = models.ForeignKey(Ingredient)
comment = models.CharField(max_length = 40, blank=True)
def __unicode__(self):
return self.id
And I have the following form:
class RecipeIngredientForm(ModelForm):
class Meta:
model = RecipeIngredient
def __init__(self, *args, **kwargs):
super(RecipeIngredientForm, self).__init__(*args, **kwargs)
self.fields['quantity'].widget = forms.TextInput(attrs={'size':'6'})
self.fields['ingredient'].widget = forms.TextInput(attrs={'size':'30'})
self.fields['comment'].widget = forms.TextInput(attrs={'size':'38'})
When I view the form, the ingredient is displayed by its id value, not its name. How can I display the name, rather than the id?
UPDATE
A solution (more elegant ideas still welcome) is to subclass the TextInput widget and use the value to get the Ingredient name:
class IngredientInput(forms.TextInput):
def render(self, name, value, attrs=None):
new=Ingredient.objects.get(pk=value).name
value=new
if value is None:
value = ''
final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
if value != '':
# Only add the 'value' attribute if a value is non-empty.
final_attrs['value'] = force_unicode(self._format_value(value))
return mark_safe(u'<input%s />' % flatatt(final_attrs))
I solved this use case by overriding a field's queryset within __init__ on the Form. A select input is still displayed but it only has one option. I had the same issue as the OP by too many options for the select.
class PaymentForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(PaymentForm, self).__init__(*args, **kwargs)
instance = kwargs.get("instance", None)
if instance and instance.id and instance.reconciled_by:
self.fields["reconciled_by"].queryset = User.objects.filter(
id=instance.reconciled_by.id
)
I had a similar problem and solved very similarly like this (Python 3) this also used the super class to do the rendering rather than rewriting it out again.
I have added a feature which I wanted which is to make the field read only, I left it it as I thought it might be useful for editing for what you want:
class IngredientInput(forms.TextInput):
def render(self, name, value, attrs=None):
new=Ingredient.objects.get(pk=value).name
value = self._format_value(new)
attrs['readonly'] = True
return super(IngredientInput, self).render(name, value, attrs)
IT displays the id because you said so:
class RecipeIngredient(models.Model):
def __unicode__(self):
return self.id
EDIT:
...and also, because you use a TextInput
self.fields['ingredient'].widget = forms.TextInput(attrs={'size':'30'})
I guess you need this:
https://docs.djangoproject.com/en/1.3/ref/forms/fields/#modelchoicefield