Errors including the custom model method in Django admin - python

What I tried to do
I tried to make custom admin method to include product thumbnail preview in my admin panel. product_thumbnail is a ImageField inside the Product Model which has general information of each products that I upload on the admin panel.
I wanted to make product_thumbnail as not required. Therefore, I set null=True and blank=True in my models.py also.
class Product(TimeStampModel):
...
product_thumbnail = models.ImageField(verbose_name='상품사진', null=True, blank=True, upload_to='product_thumbnail')
I created the custom function as below and included it in list_display admin option inside of ProductAdmin model.
def thumbnail_preview(self, obj):
return mark_safe(f'<img src="{obj.product_thumbnail.url}" width="40%" />')
thumbnail_preview.short_description = '상품사진'
Which error did I get?
I got the error as below:
ValueError: The 'product_thumbnail' attribute has no file associated with it.
What I have tried to solve issue
This might be because some Product may not have product_thumbnail so that Django admin failed to load its url due to its null value.
I used try-except so that if Django fails to find product_thumbnail associated with that product, then it can throw error text.
def thumbnail_preview(self, obj):
try:
return mark_safe(f'<img src="{obj.product_thumbnail.url}" width="40%" />')
except obj.product_thumbnail is None:
return HttpResponse('No images')
thumbnail_preview.short_description = '상품사진'
Then I got another error as below:
TypeError: catching classes that do not inherit from BaseException is not allowed
I already applied migrations. How can I solve this problem?

You write except obj.product_thumbnail is None:, but this is very incorrect! After the except keyword you are supposed to specify an exception class, not write some condition. Better yet why even use exceptions here? Generally one should avoid exceptions when one can do so, considering that if a FileField / ImageField has no file then they are considered falsy you can write the below code to solve your problem:
def thumbnail_preview(self, obj):
if obj.product_thumbnail:
return mark_safe(f'<img src="{obj.product_thumbnail.url}" width="40%" />')
return HttpResponse('No images')
thumbnail_preview.short_description = '상품사진'

This means that file is not uploaded in field at all, but you are trying to get its URL which is impossible of course, just wrap in condition. See another way to fix with using templates:
https://fixexception.com/django/the-s-attribute-has-no-file-associated-with-it/

Related

How to raise multiple ValidationError in Django

I saw this post: How to raise multiple ValidationError on Django?
However I have some questions. In the accepted answer, andilabs writes:
raise ValidationError({
'field_name_1': ["Field not allowed to change"],
'field_name_2': ["Field not allowed to change"],
})
Do the values have to be in a List even though it is just one string? If so, anyone know why? Or where in the documentation it says so? I have not found it in https://docs.djangoproject.com/en/3.0/ref/forms/validation/#raising-multiple-errors.
The below code works for me and in my html template I can do {{ form.user.errors }} to have it show up in a div on submission. For who wants to know what context I am using it in, I am using it in a Form view, and inside it I have a def clean(self) method, where I override the parent clean(). Some code for reference:
class RegisterForm(forms.Form):
user = forms.CharField()
**the fields and stuff**
def clean(self):
error = {}
cleaned = super().clean()
if 'user' not in cleaned_data:
error['user'] = ['Username is empty']
**check if other fields are not there, check password length minimum, etc.**
if len(error) != 0:
raise ValidationError(error)
return cleaned
From the Doctring of the __init__ method for ValidationError in django.core.exceptions:
"""
The `message` argument can be a single error, a list of errors, or a
dictionary that maps field names to lists of errors. What we define as
an "error" can be either a simple string or an instance of
ValidationError with its message attribute set, and what we define as
list or dictionary can be an actual `list` or `dict` or an instance
of ValidationError with its `error_list` or `error_dict` attribute set.
"""
Link to source code

Django rest Framework error messages. Does not work

I am new to Django and I am working on a small project, I want an error message to be shown if the user let the field empty. the code that I wrote is not working. Can anyone help me ?
def validate_name(school: School):
if school.name is None:
raise APIException(detail='Name is mandatory.')
class SchoolService(object):
#staticmethod
def validate_create(school: School):
validate_name(school)
Django Rest Framework provides default messages for such common wanted behaviours.
You do not even need to add anything to your field as fields are required by default, unless you explicitly specify required=False
If the user does not fill that field, DRF will automatically return a json object mentioning the field is required and should be filled.
see docs
In your serializer class try adding the validation this way
from django.core.exceptions import ValidationError
def validate_name(self, value):
if value is None:
raise serializers.ValidationError('Name is mandatory')
return value

What's the standard way of saving something only if its foreign key exists?

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)

handling unique constraint for custom field in django admin

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.

In Django how to avoid boilerplate code for getting model instance by pk in a view

Here is example code:
def someview(request):
try:
instance = SomeModel.objects.get(id=request.GET.get('id'))
except SomeModel.DoesNotExist:
instance = None
except ValueError:
# This error may occur if user manually enter invalid (non-integer)
# id value (intentionally or not) in a browser address bar, e.g.
# http://example.com/?id=2_foo instead of http://example.com/?id=2
# This raises ValueError: invalid literal for int() with base 10: '2_'
instance = None
...
Is there a best practice to get a model instance by pk without writing this boilerplate code over and over? Should I use some predefined shortcut in Django or just roll my own?
I was sure that I should use Django's DetailView or SingleObjectMixin but curiously enough it doesn't handle the ValueError exception from my example https://github.com/django/django/blob/master/django/views/generic/detail.py#L50
Is it implied that I have to specify correct integer regexp for pk kwarg in urlconf? Ok, likely. But what if I get pk from request querystring?
UPD I have special logic to do with instance either it's None or not.
You can also use Django's built in shorcut get_object_or_404() that it's designed for this specifically. That function will raise an Http404 exception in case the object doesn't exist. If you want to get None instead of raising the exception, you can create a helper function to accomplish it very easily:
def get_object_or_none(klass, *args, **kwargs):
try:
return get_object_or_404(klass, *args, **kwargs)
except Http404:
return None
Hope this helps!
The first part of your try/except block can be simplified by using django-annoying:
from annoying.functions import get_object_or_None
instance = get_object_or_None(SomeModel, id=request.GET.get('id'))
FYI, you can also just extract get_object_or_None from the package (see source).
There are many generic class based views that might be helpful, in your case DetailView could work.
from django.views.generic.detail import DetailView
class SomeModelDetailView(DetailView):
model = SomeModel
You can overwrite get_object method to change default behaviour.
def get_object(self, queryset=None):
return SomeModel.objects.get(pk=self.get_pk())
And lastly if object is none you should probably display custom 404 page.

Categories