Django error: save() got an unexpected keyword argument 'toppings' - django

On the admin UI, after fillings the fields and pressing the 'save' button, I get this error
Can anyone tell me what is the issue? From what I have read, this issue is often caused by not putting this line of code super(Pizza, self).save(*args, **kwargs), yet I still get this error and am unable to understand why
FYI: I want to make each Pizza to have a default Topping of Cheese, and this for all Pizza and it should not be able to remove it
class Topping(models.Model):
name = models.CharField(max_length=64)
def __str__(self):
return(f"{self.name}")
class Pizza(models.Model):
PIZZA_SIZES = (
('S', 'Small'),
('L', 'Large'),
)
pizza_type = models.CharField(max_length=64)
pizza_size = models.CharField(max_length=1, choices=PIZZA_SIZES)
qty_toppings = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(3)], default=0)
toppings = models.ManyToManyField(Topping)
price = models.IntegerField(help_text="Price in $")
def __str__(self):
return f"Size: {self.get_pizza_size_display()}, Type: {self.pizza_type}, Number of Toppings: {self.qty_toppi\
ngs}, Price: {self.price}, Toppings: {self.toppings.in_bulk()}"
def save(self, *args, **kwargs):
# if 'toppings' not in kwargs:
# kwargs.setdefault('force_insert', True)
# kwargs.setdefault('force_update', True)
kwargs.setdefault('toppings', Topping.objects.get(name='Cheese'))
super(Pizza, self).save(*args, **kwargs)

You can't add related through M2M objects to record before it's save. Read this doc about M2M.
You can do as offered in comments:
def save(self, *args, **kwargs):
super(Pizza, self).save(*args, **kwargs)
self.toppings.add(Topping.objects.get(name='Cheese'))
Or you can set post_save signal:
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save, sender=Pizza)
def update_stock(sender, instance, **kwargs):
instance.toppings.add(Topping.objects.get(name='Cheese'))

Related

How do I set default image based on gender in django-rest-framework?

I know there are several similar questions here, but none of them seem to resolve my issue. I am using Django-Rest-Framework.
I am creating the user-profile simultaneously with the creation of user using signals.
As my question is pretty self explanatory, this is my code
models.py
from django.contrib.auth.models import AbstractUser
from django.utils.translation import ugettext_lazy as _
from django.db import models
from django.conf import settings
from PIL import Image
GENDER_SELECTION = (
('Male', 'Male'),
('Female', 'Female'),
)
class CustomUser(AbstractUser):
username = models.CharField(max_length=100, blank=True, null=True)
email = models.EmailField(_('email address'), unique=True)
gender = models.CharField(max_length=20, choices=GENDER_SELECTION)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username', 'first_name', 'last_name', 'gender']
def __str__(self):
return self.email
class UserProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='profile')
profile_pic = models.ImageField(upload_to='profile/', default='default.png', blank=True)
def __str__(self):
return f'{self.user.first_name} Profile'
def save(self, *args, **kwargs):
super(UserProfile, self).save(*args, **kwargs)
uploaded_image = Image.open(self.profile_pic.path)
if uploaded_image.height > 300 or uploaded_image.width > 300:
output_size = (300, 300)
uploaded_image.thumbnail(output_size)
uploaded_image.save(self.profile_pic.path)
This is what I have tried
In models.py
# ...
def save(self, *args, **kwargs):
super(Profile, self).save(*args, **kwargs)
if self.profile_pic == 'default.png':
if CustomUser.gender == 'Male':
self.profile_pic = 'user_default_m.png'
return self.profile_pic
else:
self.profile_pic = 'user_default_f.png'
return self.profile_pic
else:
uploaded_image = Image.open(self.profile_pic.path)
if uploaded_image.height > 300 or uploaded_image.width > 300:
output_size = (300, 300)
uploaded_image.thumbnail(output_size)
uploaded_image.save(self.profile_pic.path)
Update - Pasting signals.py file as requested
signals.py
#receiver(post_save, sender=CustomUser)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
#receiver(post_save, sender=CustomUser)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
Here is something I noticed, you are trying to resize the uploaded image and you are trying to set a default-image in the same go. Also you are trying to do this post_save.
I am assuming you are allowing upload of image by the user when he edits his/her profile which I am inferring since you have over-ridden the save method.
I would try to avoid clubbing those two. Even though they are related to Image operations, in-essence, they are different operations.
Keep your models.py file lean and restore it to your initial code.
Now, as you are already using signals, you can try pre_save in your signals.py file. Edit your signals.py file and add the following code snippet
#receiver(pre_save, sender=UserProfile)
def set_profile_image(sender, instance, *args, **kwargs):
# Obtain the gender of the just created user
gender = CustomUser.objects.all().last().gender
# Check for the gender and assign the file
if gender == 'Male' and instance.profile_pic == 'default.png':
instance.profile_pic = 'default_m.png'
elif gender == 'Female' and instance.profile_pic == 'default.png':
instance.profile_pic = 'default_f.png'
This should work.

Call a method for each model instance

I have a Category model. I want to make a directory for the category everytime I create a new category. I have a method in my model called create_directory.
class Category(models.Model):
category_title = models.CharField(max_length=200)
category_image = models.ImageField(upload_to="category")
category_description = models.TextField()
slug = models.SlugField(max_length=200, unique=True, default=1)
def create_directory(self):
gallery_path = os.path.abspath(
os.path.join(settings.MEDIA_ROOT, Category.slug))
if not os.path.isdir(gallery_path):
os.mkdir(gallery_path)
class Meta:
verbose_name_plural = "Categories"
unique_together = ("category_title", "slug")
def __str__(self):
return self.category_title
I want to call create_directory each time I create a category in the Admin panel.
First, I think you meant self.slug and not Category.slug. self.slug is the value of the slug field for that particular instance, while Category.slug is just an instance of the SlugField class.
You should override your model's save method to call create_directory:
class Category(models.Model):
...
def create_directory(self):
gallery_path = os.path.abspath(
os.path.join(settings.MEDIA_ROOT, self.slug))
if not os.path.isdir(gallery_path):
os.mkdir(gallery_path)
def save(self, *args, **kwargs):
if not self.pk:
self.create_directory()
super().save(*args, **kwargs)
Alternatively, you can create a signal for this:
models.py:
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save, sender=Category)
def category_post_save(sender, instance, *args, **kwargs):
gallery_path = os.path.abspath(
os.path.join(settings.MEDIA_ROOT, instance.slug))
if not os.path.isdir(gallery_path):
os.mkdir(gallery_path)
Update
If you want to remove the directory as well, you can override the delete method:
class Category(models.Model):
...
def delete(self, *args, **kwargs):
os.rmdir(os.path.join(settings.MEDIA_ROOT, self.slug))
super().delete(*args, **kwargs)
There is also a pre_delete signal that you can use.
I want to call create_directory each time I create a category in the Admin panel.
As you want to call the create_directory method when a category is created from the admin panel, you should use save_model and not save.
You might want to read Difference between save() and save_model().
Simply you can do:
def save_model(self, request, obj, form, change):
self.create_directory()
super().save_model(request, obj, form, change)

django - logging edit event - need to get id of logged in user

I've a product-stock model as given below.
TRANSACTION_TYPE=(('I','Stock In'),('O','Stock Out'))
class Stock(models.Model):
product=models.ForeignKey('product.Product', blank=False,null=False)
date=models.DateField(blank=False, null=False,)
quantity=models.PositiveIntegerField(blank=False, null=False)
ttype=models.CharField(max_length=1,verbose_name="Transaction type",choices=TRANSACTION_TYPE, blank=False, db_index=True)
I need to log the update activity on this model, along with the id of the user who updated it.
ACTIONS=(('EC','Edit Category'),
('EG','Edit Group'),
('EP','Edit Product'),
('ES','Edit Stock'))
class MyLog(models.Model):
user=models.ForeignKey(auth.models.User, blank=False)
action= models.CharField(max_length=2, choices=ACTIONS, null=False,blank=False)
date=models.DateTimeField(blank=False, auto_now=True)
data = JSONField()
I've the added following code to the Stock model.
def __init__(self, *args, **kwargs):
super(Stock, self).__init__(*args, **kwargs)
if self.pk != None :
self.__important_fields = ['product','date', 'quantity', 'ttype', ]
for field in self.__important_fields:
setattr(self, '__original_%s' % field, getattr(self, field))
field_name='__original_%s' % field
def save(self, *args, **kwargs):
if self.pk != None :
print("Editing")
flag=False
log=MyLog(user=?,action='ES')
log.data=[]
for field in self.__important_fields:
original=getattr(self, '__original_%s' % field)
if original != getattr(self, field):
flag=True
log.data.append({field : str(original)})
if flag:
log.save()
else:
print("Adding")
super(Stock, self).save(*args, **kwargs)
This works, when I hard code a user object into the line log=MyLog(user=?,action='ES').
I need to log the id of the user who performed this edit operation.
How can I achieve this?
Thanks.
Here's how I finally achieved my goal.
Instead of logging the event from the model, I switched my code to the forms.
Here's my final code.
mylog app model
ACTIONS=(('EC','Edit Category'),
('EG','Edit Group'),
('EP','Edit Product'),
('ES','Edit Stock'))
class MyLog(models.Model):
user=models.ForeignKey(settings.AUTH_USER_MODEL, blank=False)
model_id=models.IntegerField(default=0)
action= models.CharField(max_length=2, choices=ACTIONS, null=False,blank=False)
date=models.DateTimeField(blank=False, auto_now=True)
old_data = JSONField(default=None)
new_data = JSONField(default=None)
stock app - update view
class UpdateStock(UpdateView):
model=Stock
form_class=UpdateStockForm
def get_form_kwargs(self):
kwargs = super( UpdateStock, self).get_form_kwargs()
kwargs.update({'user_id': self.request.user.id})
return kwargs
stock app - update form
class UpdateStockForm(forms.ModelForm):
def __init__(self,pk= None, *args, **kwargs):
self.user_id=kwargs.pop('user_id')
super(UpdateStockForm, self).__init__(*args, **kwargs)
def clean(self):
cleaned_data = super(UpdateStockForm, self).clean()
quantity = cleaned_data.get('quantity')
date= cleaned_data.get('date')
priv_quantity=self.instance.quantity
priv_date=self.instance.date
if priv_quantity!=quantity or priv_date != date:
#log!
log=MyLog(user=auth.models.User.objects.get(pk=self.user_id),action='ES', model_id=self.instance.id)
log.old_data=[]
log.old_data.append({'date' : str(priv_date), 'quantity':priv_quantity })
log.new_data=[]
log.new_data.append({ 'date' : str(date), 'quantity':quantity })
log.save()
return cleaned_data

Auto Generated Slugs in Django Admin

I have an app that will one day allow front-end crud, which will create the slug with slugify. Right now though, all the object creation is being done in the admin area and I was wondering if there is a way to auto generate slugs while creating and saving an object from within admin?
Here is the method for slugify for the front-end; not sure if its even relevant. Thank you.
def create_slug(instance, new_slug=None):
slug = slugify(instance.title)
if new_slug is not None:
slug = new_slug
qs = Veteran.objects.filter(slug=slug).order_by('-id')
exists = qs.exists()
if exists:
new_slug = '%s-%s' % (slug, qs.first().id)
return create_slug(instance, new_slug=new_slug)
return slug
Having just used this on another answer, I have exactly the right code in my clipboard. I do exactly this for one of my models:
from django.utils.text import slugify
class Event(models.Model):
date = models.DateField()
location_title = models.TextField()
location_code = models.TextField(blank=True, null=True)
picture_url = models.URLField(blank=True, null=True, max_length=250)
event_url = models.SlugField(unique=True, max_length=250)
def __str__(self):
return self.event_url + " " + str(self.date)
def save(self, *args, **kwargs):
self.event_url = slugify(self.location_title+str(self.date))
super(Event, self).save(*args, **kwargs)
Above solutions break validation in the Django Admin interface. I suggest:
from django import forms
from django.http.request import QueryDict
from django.utils.text import slugify
from .models import Article
class ArticleForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ArticleForm, self).__init__(*args, **kwargs)
# Ensure that data is a regular Python dictionary so we can
# modify it later.
if isinstance(self.data, QueryDict):
self.data = self.data.copy()
# We assume here that the slug is only generated once, when
# saving the object. Since they are used in URLs they should
# not change when valid.
if not self.instance.pk and self.data.get('title'):
self.data['slug'] = slugify(self.data['title'])
class Meta:
model = Article
exclude = []

Overriding save() method in Model. How to do this correctly?

I try overriding save() method in Model. How to do this correctly?
import Image
from tesseract import image_to_string
class FileModel(models.Model):
user = models.ForeignKey(User)
base_file = models.FileField(upload_to="files")
after_file = models.FileField(upload_to="file_ocr", blank=True, null=True)
def save(self):
after_file = image_to_string(Image.open('base_file'), lang='en')
after_file.save()
super(FileModel, self).save()
If base_file is created I need use image_to_string() method and save result like after_file.
def save(self, *args, **kwargs):
creating = self.pk is None
super(FileModel, self).save(*args, **kwargs)
if creating:
self.after_file = image_to_string(Image.open('base_file'), lang='en')
self.after_file.save()
or with signals:
from django.db.models import signals
from django.dispatch import receiver
#receiver(signals.post_save, sender=FileModel)
def create_file(sender, instance, created, *args, **kwargs):
if created:
instance.after_file = image_to_string(Image.open('base_file'), lang='en')
instance.after_file.save()