Update only specific fields in a models.Model - django

I have a model
class Survey(models.Model):
created_by = models.ForeignKey(User)
question = models.CharField(max_length=150)
active = models.NullBooleanField()
def __unicode__(self):
return self.question
and now I want to update only the active field. So I do this:
survey = get_object_or_404(Survey, created_by=request.user, pk=question_id)
survey.active = True
survey.save(["active"])
Now I get an error IntegrityError: PRIMARY KEY must be unique.
Am I right with this method to update?

To update a subset of fields, you can use update_fields:
survey.save(update_fields=["active"])
The update_fields argument was added in Django 1.5. In earlier versions, you could use the update() method instead:
Survey.objects.filter(pk=survey.pk).update(active=True)

Usually, the correct way of updating certain fields in one or more model instances is to use the update() method on the respective queryset. Then you do something like this:
affected_surveys = Survey.objects.filter(
# restrict your queryset by whatever fits you
# ...
).update(active=True)
This way, you don't need to call save() on your model anymore because it gets saved automatically. Also, the update() method returns the number of survey instances that were affected by your update.

Related

Setting Djando field default value based on another field

I am creating a Django model where:
expirationTimeStamp field's default value is based on creationTimeStamp
isLive boolean field value is based on expirationTimeStamp
I have written the following functions expirationTimeCalculation and postLiveStatus and assigned them as default values to the fields but I am getting error. I have also tried to assign to respective fields through property(function) yet I am still getting error.
One of the functionality that I need to implement is that user can send custom expirationTimeStamp as well that would override default value, therefore, I believe property(function) assignment is not ideal for expirationTimeStamp field.
Is there any other way that I can go about to set the expirationTimeStamp field value based on creationTimeStamp field?
Any feedback is appreciated!
class Posts(models.Model):
def expirationTimeCalculation(self):
EXPIRATION_DURATION = 86400 #time in seconds
expirationTime = self.creationTimestamp + timedelta(seconds = EXPIRATION_DURATION)
return expirationTime
def postLiveStatus(self):
return (self.expirationTimestamp > timezone.now)
message = models.TextField()
creationTimestamp = models.DateTimeField(default=timezone.now)
expirationTimestamp = models.DateTimeField(default=expirationTimeCalculation)
isLive = models.BooleanField(default=postLiveStatus)
Similar answered question. I am attaching the official documentation as well as the link to the answered question.
Models certainly do have a "self"! It's just that you're trying to define an attribute of a model class as being dependent upon a model instance; that's not possible, as the instance does not (and cannot) exist before your define the class and its attributes.
To get the effect you want, override the save() method of the model class. Make any changes you want to the instance necessary, then call the superclass's method to do the actual saving. Here's a quick example.
def save(self, *args, **kwargs):
if not self.subject_init:
self.subject_init = self.subject_initials()
super(Subject, self).save(*args, **kwargs)
Model Overriding Documentation
Django Model Field Default Based Off Another Field
I needed field end_day to default to the last day of month in serializer. So I did this:
class MySerializer(serializers.Serializer):
year = serializers.IntegerField()
month = serializers.IntegerField()
end_day = serializers.IntegerField(required=False, default=None)
def end_day_value(self):
return self.validated_data['end_day'] or \
(datetime(year=self.validated_data['year'], month=self.validated_data['month'])+relativedelta(months=1)-relativedelta(days=1)).day
So when I need end_day or its default value I just call end_day_value method.

Django REST Serializer - Partial update still updates full record

I am trying to partially update a record using (partial=True) via my serializer, however, when I look at the sql update statement, it's showing that all fields are being updated when only a subset of fields are being submitted.
class Setting(models.Model):
comments_enabled = models.BooleanField(default=True)
visibility = models.CharField(max_length=50, choices=VISIBILITIES,
blank=False, null=False,
default=VISIBILITY_CHOICE_PARTICIPANTS)
modified = models.DateTimeField(auto_now=True, blank=True, null=True)
class SettingsSerializer(serializers.ModelSerializer):
class Meta:
model = Setting
fields = ('id', 'comments_enabled', 'visibility', 'modified')
class SomeSerializer(serializers.ModelSerializer):
def update(self, instance, validated_data):
settings_serializer = SettingsSerializer(instance.settings, data=validated_data.get('settings'), partial=True)
settings_serializer.is_valid(raise_exception=True)
settings_serializer.save()
I have doubled checked that the validated_data dictionary being passed in only has one field.
I'm using DRF 3.3 and Django 1.9
I think I got your question now. As stated in the docs you can use the partial keyword to allow partial updates.
By default, serializers must be passed values for all required fields or they will raise validation errors. You can use the partial argument in order to allow partial updates.
This only means that you don't have to post the full object as JSON. As far as I can see the partial argument is only used for validation. In the end the update method of the serializer gets called:
def update(self, instance, validated_data):
raise_errors_on_nested_writes('update', self, validated_data).
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
return instance
Here the values from validated_data are assigned to the instance. In the end the objects save method is called. And in this method django creates the SQL statement to update all values, because django doesn't know which value changed.

Model Method from rest_framework modelSerializer

this is a simple question but I'm very new to django-rest-framework.
I was wondering if there is any way to access a method defined on the model from the serializer.?
Say I have a model
class Listing(models.Model):
listingid = models.BigIntegerField(primary_key=True)
mappingid = models.BigIntegerField()
projectlevelid = models.IntegerField()
subsellerid = models.IntegerField()
iscreatedbyadmin = models.BooleanField(default=None, null=True)
createdon = models.DateTimeField(auto_now_add=True, editable=False)
validationstatus = models.SmallIntegerField(default=0)
def is_project(self):
""" Returns True if listing is of Project Type (projectlevelid=6) else False"""
if self.projectlevelid == 6:
return True
else:
return False
def get_project_info(self):
"""Returns False if listing is not mapped to a project, else returns the project info"""
if self.is_project() == False:
return False
return models.Project.objects.get(projectid=self.mappingid)
Is it possible for the serializer
class ListingSerializer(serializers.ModelSerializer):
class Meta:
model = models.MasterListing
to have access to Listing.is_project i.e. for an object of the Listing model, can the serializer call its is_project method?
If so, can I set a field in the serializer such that if is_project returns true, the field is populated?
I am trying for something like this,
class ListingSerializer(serializers.ModelSerializer):
project = serializers.SomeRELATEDFieldTYPE() # this field if populated if the `is_project` is true
class Meta:
model = models.MasterListing
I understand I can do this using some combination of required=False and SerializerMethodField, but maybe there is a simpler way?.
Note: It is not possible for me to set a foreign key to the mappingid, since it depends on the projectlevelid. I also can't affect this relationship so no further normalization is possible. I know that there might be some way using content-types, but we are trying to avoid that if it is possible..
EDIT: I solved the problem, but not as the question specified.
I used this:
class ListingSerializer(serializers.ModelSerializer):
project = serializers.SerializerMethodField()
def get_project(self, obj):
"""Returns False if listing is not mapped to a project, else returns the project info"""
if str(obj.projectlevelid) == str(6):
projectObj = models.Project(projectid=obj.mappingid)
projectObjSerialized = ProjectSerializer(projectObj)
return projectObjSerialized.data
return False
class Meta:
model = models.MasterListing
So, the original question still stands: "Is it possible for the modelSerializer to access its models methods?"
Also, another problem that now appears is, can I make the serializer exclude fields on demand, i.e. can it exclude mappingid and projectlevelid if it is indeed a project?
For your first question source attribute is the answer, citing:
May be a method that only takes a self argument, such as
URLField('get_absolute_url')
For your second answer, yes it is also possible. Check the example it provides in their docs: http://www.django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields
PS: I really love drf for its very complete documentation =).
EDIT
To use the source attribute you can just declare a new explicit field like so:
is_project = serializers.BooleanField(source='is_project')
With this, is_project field has the value of the is_project method of your instance. Having this, when creating the dynamic serializer (by modifying its init method) you can add the 'project' field if it's True.
#argaen is absolutely right, source is a DRF core argument, and would most definitely solve your problem. However, it's redundant to use source, if the field name is the same as the source. So the above answer won't require you specify source, since field name is_project is the same as source name is_project.
So in your case:
is_project = serializers.BooleanField()

Django unique_together doesn't work with ForeignKey=None

I saw some ppl had this problem before me, but on older versions of Django, and I'm running on 1.2.1.
I have a model that looks like:
class Category(models.Model):
objects = CategoryManager()
name = models.CharField(max_length=30, blank=False, null=False)
parent = models.ForeignKey('self', null=True, blank=True, help_text=_('The direct parent category.'))
class Meta:
unique_together = ('name', 'parent')
Whenever i try to save in the admin a category with a parent set to None, it still works when there's another category with the SAME name and parent set to None.
Ideas on how to solve this gracefully?
The unique together constraint is enforced at the database level, and it appears that your database engine does not apply the constraint for null values.
In Django 1.2, you can define a clean method for your model to provide custom validation. In your case, you need something that checks for other categories with the same name whenever the parent is None.
class Category(models.Model):
...
def clean(self):
"""
Checks that we do not create multiple categories with
no parent and the same name.
"""
from django.core.exceptions import ValidationError
if self.parent is None and Category.objects.filter(name=self.name, parent=None).exists():
raise ValidationError("Another Category with name=%s and no parent already exists" % self.name)
If you are editing categories through the Django admin, the clean method will be called automatically. In your own views, you must call category.fullclean().
I had that problem too and solved it by creating a supermodel with clean method (like Alasdair suggested) and use it as base class for all my models:
class Base_model(models.Model):
class Meta:
abstract=True
def clean(self):
"""
Check for instances with null values in unique_together fields.
"""
from django.core.exceptions import ValidationError
super(Base_model, self).clean()
for field_tuple in self._meta.unique_together[:]:
unique_filter = {}
unique_fields = []
null_found = False
for field_name in field_tuple:
field_value = getattr(self, field_name)
if getattr(self, field_name) is None:
unique_filter['%s__isnull'%field_name] = True
null_found = True
else:
unique_filter['%s'%field_name] = field_value
unique_fields.append(field_name)
if null_found:
unique_queryset = self.__class__.objects.filter(**unique_filter)
if self.pk:
unique_queryset = unique_queryset.exclude(pk=self.pk)
if unique_queryset.exists():
msg = self.unique_error_message(self.__class__, tuple(unique_fields))
raise ValidationError(msg)
Unfortunately, for those of us using PostgreSQL as our backend database engine, there will never have a fix for this issue:
"Currently, only B-tree indexes can be declared unique.
When an index is declared unique, multiple table rows with equal indexed values are not allowed. Null values are not considered equal. A multicolumn unique index will only reject cases where all indexed columns are equal in multiple rows.
PostgreSQL automatically creates a unique index when a unique constraint or primary key is defined for a table. The index covers the columns that make up the primary key or unique constraint (a multicolumn index, if appropriate), and is the mechanism that enforces the constraint."
Source: https://www.postgresql.org/docs/9.0/indexes-unique.html

Django: models last mod date and mod count

I have a django model called Blog.
I'd like to add a field to my current model that is for last_modified_date. I know how to set a default value, but I would like somehow for it to get automatically updated anytime I modify the blog entry via the admin interface.
Is there some way to force this value to the current time on each admin site save?
Also would there be some way to add a mod_count field and have it automatically calculated on each modify of the admin site blog entry?
Create a DateTimeField in your model. Have it update whenever it is saved. This requires you to use the auto_now_add option:
class DateTimeField([auto_now=False, auto_now_add=False, **options])
DateTimeField.auto_now_add¶
Automatically set the field to now every time the object is saved. Useful
for "last-modified" timestamps. Note
that the current date is always used;
it's not just a default value that you
can override.
It should look something like this:
class Message(models.Model):
message = models.TextField()
active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
Model field reference
For the second part, I think you have to overload
ModelAdmin.save_model(self, request, obj, form, change)
As James Bennett describes here. It will look something like this:
class EntryAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
if change:
obj.change_count += 1
obj.save()
The accepted answer is no longer correct.
For newer django versions, you will have to use the auto_now=True parameter rather than the auto_now_add=True, which will only set the field value when the object is initially created.
From the documentation:
DateField.auto_now_add¶
Automatically set the field to now when the
object is first created. Useful for creation of timestamps.
The desired functionality is now implemented by auto_now:
DateField.auto_now¶
Automatically set the field to now every time the
object is saved.
So to achieve self-updating timestamps a model should be created like this:
class Message(models.Model):
message = models.TextField()
active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now=True)
mod_count = models.IntegerField(default=0)
To increment mod_count everytime this model is modified overload the model's save() method:
def save(self, *args, **kwargs):
self.mod_count +=1
return super(Message,self).save(*args, **kwargs)
There's a number of ways you can increase the edit count each time it's saved.
The model itself has a save() method, and the admin model has a model_save() method.
So for example, let's say you wanted it to increment when it was edited with the admin tool....
models.py:
class MyModel(models.Model):
edit_count = models.IntegerField()
# ... rest of model code here...
admin.py:
class MyModelAdmin(admin.ModelAdmin)
def save_model(self, request, obj, form, change):
if not change:
obj.edit_count = 1
else:
obj.edit_count += 1
obj.save()
You could do similar code off of the model save() event as well.
Something else you may be interested in is django-command-extensions. It adds 2 fields which may be helpful to you:
CreationDateTimeField - DateTimeField that will automatically set it's date when the object is first saved to the database.
ModificationDateTimeField - DateTimeField that will automatically set it's date when an object is saved to the database.
You can also use a middleware solution found here: https://bitbucket.org/amenasse/django-current-user/src/7c3c90c8f5e854fedcb04479d912c1b9f6f2a5b9/current_user?at=default
settings.py
....
MIDDLEWARE_CLASSES = (
....
'current_user.middleware.CurrentUserMiddleware',
'current_user.middleware.CreateUserMiddleware',
)
....
INSTALLED_APPS = (
'current_user',
....
....
)
models.py
class ExampleModel(models.Model):
foo = models.IntegerField()
last_user = CurrentUserField(related_name="+")
created_by = CreateUserField(related_name="+")