I'm using Python 3.7 and Django . I have the following model, with a foreign key to another model ...
class ArticleStat(models.Model):
objects = ArticleStatManager()
article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='articlestats')
...
def save(self, *args, **kwargs):
if self.article.exists():
try:
article_stat = ArticleStat.objects.get(article=self.article, elapsed_time_in_seconds=self.elapsed_time_in_seconds)
self.id = article_stat.id
super().save(*args, **kwargs, update_fields=["hits"])
except ObjectDoesNotExist:
super().save(*args, **kwargs)
I only want to save this if the related foreign key exists, otherwise, I've noticed errors result. What's the standard Django/Python way of doing something like this? I thought I read I could use ".exists()" (Check if an object exists), but instead I get an error
AttributeError: 'Article' object has no attribute 'exists'
Edit: This is the unit test I have to check this ...
id = 1
article = Article.objects.get(pk=id)
self.assertTrue(article, "A pre-condition of this test is that an article exist with id=" + str(id))
articlestat = ArticleStat(article=article, elapsed_time_in_seconds=250, hits=25)
# Delete the article
article.delete()
# Attempt to save ArticleStat
articlestat.save()
If you want to be sure Article exists in ArticleStat's save method you can try to get it from your database and not just test self.article.
Quoting Alex Martelli:
" ... Grace Murray Hopper's famous motto, "It's easier to ask forgiveness than permission", has many useful applications -- in Python, ... "
I think using try .. except .. else is more pythonic and I will do something like that:
from django.db import models
class ArticleStat(models.Model):
...
article = models.ForeignKey(
Article, on_delete=models.CASCADE, related_name='articlestats'
)
def save(self, *args, **kwargs):
try:
article = Article.objects.get(pk=self.article_id)
except Article.DoesNotExist:
pass
else:
try:
article_stat = ArticleStat.objects.get(
article=article,
elapsed_time_in_seconds=self.elapsed_time_in_seconds
)
self.id = article_stat.id
super().save(*args, **kwargs, update_fields=["hits"])
except ArticleStat.DoesNotExist:
super().save(*args, **kwargs)
If you are using a relational database, foreign key constraints will be added automatically post-migration. save method may not need any customization.
class ArticleStat(models.Model):
objects = ArticleStatManager()
article = models.ForeignKey(
Article, on_delete=models.CASCADE, related_name='articlestats'
)
Use the following code to create ArticleStats
from django.db import IntegrityError
try:
ArticleStats.objects.create(article=article, ...)
except IntegrityError:
pass
If article_id is valid, ArticleStats objects get created else IntegrityError is raised.
article = Article.objects.get(id=1)
article.delete()
try:
ArticleStats.objects.create(article=article, ...)
print("article stats is created")
except IntegrityError:
print("article stats is not created")
# Output
article stats is not created
Note: Tested on MySQL v5.7, Django 1.11
article field on your ArticleStat model is not optional. You can't save your ArticleStat object without the ForeignKey to Article
Here is a similar code, item is a ForeignKey to the Item model, and it is required.
class Interaction(TimeStampedModel, models.Model):
...
item = models.ForeignKey(Item, on_delete=models.CASCADE, related_name='interactions')
type = models.IntegerField('Type', choices=TYPE_CHOICES)
...
If I try to save an object of Interaction from the shell without selecting a ForeignKey to the item, I receive an IntegrityError.
~ interaction = Interaction()
~ interaction.save()
~ IntegrityError: null value in column "item_id" violates not-null constraint
You don't need a check self.article.exists(). Django and Database will require that field and will not let you save the object without it.
You should read about ForeignKey field in Django Docs
You can just test the value of the article field. If it's not set, I believe it defaults to None.
if self.article: # Value is set
If you want this ForeignKey field to be optional (which it sounds like you do), you need to set blank=True and null=True on that field. This will allow the field to be blank (in validation) and will set null on the field when it's not there.
As mentioned in the comments below, your database is likely enforcing the fact that the field is required, and refuses to remove the article instance.
As other answers have pointed out, the Article ForeignKey is required on your ArticleStat model and saving will automatically fail without a valid Article instance. The best way to fail gracefully with non-valid input is using Form validation with Django's Forms API. Or if handling serialized data with Django Rest Framework, using a Serializer, which is the Form-like equivalent for JSON data. That way you don't need to overwrite the save method unless you have a specific requirement.
So far nobody has mentioned the correct usage of .exists(). It is a method of a queryset not a model instance, which is why you get the error you mentioned above when trying to apply it to an individual model instance with self.article.exists(). To check the existence of an object, simply use .filter instead of .get. If your Article (pk=1) exists then:
Article.objects.filter(pk=1)
Will return a queryset with one Article in it:
<Queryset: [Article: 1]>
and
Article.objects.filter(pk=1).exists()
Will return True. Whereas if the item does not exist the query will return an empty queryset and .exists() will return False, rather than raising an exception (as attempting to .get() a non-existent object does). This still applies if the pk previously existed and has been deleted.
EDIT: Just noticed that your ArticleStat's on_delete behaviour is currently set to CASCADE. This means that when an Article is deleted, the related ArticleStat is also deleted. So I think you must have been misinterpreting the errors/difficulties you mentioned in reply to #jonah-bishop's answer when trying if self.article:. For a minimal fix, if you still want to keep the ArticleStat after an Article is deleted, change the on_delete keyword to models.SET_NULL, and as per Jonah's answer, add the extra keywords null=True, blank=True:
article = models.ForeignKey(Article, on_delete=models.SET_NULL, related_name='articlestats', null=True, blank=True)
Then there should be no problem simply doing if self.article: to check for a valid ForeignKey object relation. But using Forms/Serializers is still better practice though.
The code of last line articlestat.save() will fail if the article instance has been deleted. Django and database will check the article automatically for you if you are using relation database like mysql or sqlite3.
During the migrations, a constraint will be created. For example:
shell>>> python manage.py sqlmigrate <appname> 0001
CREATE TABLE impress_impress ...
...
ALTER TABLE `impress_impress` ADD CONSTRAINT
`impress_impress_target_id_73acd523_fk_account_myuser_id` FOREIGN KEY (`target_id`)
REFERENCES `account_myuser` (`id`);
...
So if you want to save the articlestat without article, an error will be raised.
You can call .full_clean() before .save()
from django.core.exceptions import ValidationError
class ArticleStat(models.Model):
#...
def save(self, *args, **kwargs):
try:
self.full_clean()
except ValidationError as e:
# dont save
# Do something based on the errors contained in e.message_dict.
# Display them to a user, or handle them programmatically.
pass
else:
super().save(*args, **kwargs)
Let's assume I have a model like this:
class Data(models.Model):
a = models.CharField()
b = models.CharField()
c = models.IntegerField()
I would like to setup a serializer in such a way that it automatically fills in field c and it is not required for a POST. I tried to overwrite the create function of the serializer, but it doesn't work:
class DataSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Data
fields = ('a', 'b')
def create(self, validated_data, **kwargs):
Data.objects.c = 5
return Data.objects.create(**validated_data)
However, if I try this, I end up with an IntegrityError: NOT NULL constraint failed: model_data.c. What is the syntax that I have to use here?
EDIT: Updated formatting.
The reason you're getting the error because field c is not set to null = True - as such an error is raised at the validation stage even before the serializer hits the create method.
Bear in mind that the process goes like this:
Submit serializer data
field-level validation happens - this includes checks for null integrity, min/max length etc and also any custom field validations defined in def validate_<field_name>
object-level validation happens - this calls the def validate method
validated data is passed to the save method, depending on how you designed the serializer - it will save the instance, or route the data to either create or update
All of the info regarding this can be found in Django's and DRF's docs.
A few things to consider:
are you setting a global default for that field? If so, set the default in your models - c = models.IntegerField(default=a_number_or_a_callable_that_returns_an_integer)
do you intend to display the field? If so, include c in your fields and add one more Meta attribute - read_only_fields = ('c',)
If it's neither of the above, you might want to override the validate_c method
Apologies for the poor formatting, typing it on my phone - will update once I get to a computer
In your code Data.objects.c = 5 does nothing.
If you want to set this value yourself use validated_data['c'] = 5 or Data.objects.create(c=5, **validated_data) (just not both at the same time).
Rather than doing this in the serializer, there are hooks in the generic views that allow you to pass values to the serializer. So in your case you might have:
class DataViewSet(ModelViewSet):
# ...
def perform_create(self, serializer):
serializer.save(c=5)
See the "Save and deletion hooks" section here
I have the tastypie resource:
class ProjectPermissionResource(ModelResource):
project = fields.ToOneField(ProjectResource, 'project', full=True, readonly=True)
...
As you see, the project resource is returning full with all fields.
I need to remove some fields, so I tried to use the dehydrate_project method:
def dehydrate_project(self, bundle):
# delete some project fields here
return bundle
But after adding this method I get "maximum recursion depth exceeded while calling a Python object". Stucked after this.
Need to delete some fields from project (in details I need only ot id, name and so on...)
You can construct your own object from bundle.data
So basically something like this:
return bundle.data = {stuffs}
Hi I'm new to django and just took my first foray into using a custom field for my model. I have a Char field that I want to always be saved as lower case so I implemented it as a custom field as follows (learned from another Stack Overflow post):
from django.db import models
from django.db.models.fields import CharField
class LowercaseCharField(CharField):
def pre_save(self, model_instance, add):
current_value = getattr(model_instance, self.attname)
setattr(model_instance, self.attname, current_value.lower())
return getattr(model_instance, self.attname)
class Item(models.Model):
name = LowercaseCharField(max_length=50, unique=True)
def __str__(self):
return self.name
I've tested this out in admin and indeed a field entry gets correctly converted to lowercase before it's saved. Unfortunately, when I tested the uniqueness constraint, admin isn't handling the Integrity Error gracefully. Instead of getting the clean error like I do if it's an exact case match from the get go:
I get the ugly error page:
How do I go about setting the custom field in such a way that the unique constraint is caught "early" enough to trigger the graceful error, or otherwise modify the admin so that this "later" error is handled more gracefully?
(Note: I am just using sqlite3 for my db at the moment)
UPDATE:
In case any one is interested, here's the modified code that worked for me:
class LowercaseCharField(CharField):
def get_db_prep_value(self, value, connection, prepared=False):
return value.lower()
I don't think you'll make it by overriding pre_save, because pre_save gets called after uniqueness validation has occurred.
Try with the other methods, such as get_db_prep_save or get_db_prep_value.
I am using Tastypie for non-ORM data source (Amazon Dynamodb). I have gone through the official documentation for non-ORM source and found the following code:
class MessageResource(Resource):
# Just like a Django ``Form`` or ``Model``, we're defining all the
# fields we're going to handle with the API here.
uuid = fields.CharField(attribute='uuid')
user_uuid = fields.CharField(attribute='user_uuid')
message = fields.CharField(attribute='message')
created = fields.IntegerField(attribute='created')
I am new to Tastypie and what I understand is that fields uuid, message, created.. which are returned by API are defined over here. Is there any way that I return those fields that are not defined here i.e. all those fields returned by the dictionary in obj_get_list or obj_get.
You can use the dehydrade method. Simply add a new key to bundle.data.
def dehydrate(self, bundle):
for item in bundle.obj.iteritems():
bundle.data["new_key"] = "new_value"
return bundle