Call a method for each model instance - django

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)

Related

Django DetailView but get_object_or_404() instead of get_object()

I have Django Model that has a live boolean attribute.
I want the View to get the Model by slug and only go to this page if it's live USING THE DetailView (not function based view, because I want to see how to do it.
Model definition
# myapp/models.py
class MyModel(models.Model):
name = models.CharField(max_length=255)
live = models.BooleanField(default=True)
slug = models.SlugField()
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
I hoped it would be done something like this:
class ModelDetailView(DetailView):
model = MyModel
def get(self, request, *args, **kwargs):
service = self.get_object_or_404(Service, live=True) # <- Main point of what I'm looking for help with
return super().get(request, *args, *kwargs)
Is there a way to filter this way?
You can specify the queryset to filter, so:
class ModelDetailView(DetailView):
model = MyModel
queryset = MyModel.objects.filter(live=True)
You thus do not need to implement the .get(…) method at all.

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

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'))

How to override queryset used by Django admin/form for a Foreign Key

I've tried everything I can find on the internet here, and nothing seems to work, so wondering if lots of the previous answers are for old versions. I'm on Django 2.2.9.
#models.py
class ParentModel(models.Model):
title = models.CharField()
class ChildModel(models.Model):
parent = models.ForeignKey(
ParentModel,
on_delete=models.CASCADE,
related_name='parent'
)
# admin.py
#admin.register(ParentModel)
class ParentModelAdmin(admin.ModelAdmin):
model = ParentModel
def get_queryset(self, request):
return ParentModel.objects.get_complete_queryset()
class ChildModelForm(forms.Form):
def __init__(self, u, *args, **kwargs):
super(ChildModelForm, self).__init__(*args, **kwargs)
self.fields['parent'].queryset = ParentModel.objects.get_complete_queryset()
class Meta:
model = ChildModel
fields = '__all__'
#admin.register(ChildModel)
class ChildModelAdmin(admin.ModelAdmin):
model = ChildModel
form = ChildModelForm
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "parent":
kwargs["queryset"] = ParentModel.objects.get_complete_queryset()
return super().formfield_for_foreignkey(db_field, request, **kwargs)
I have a manager query called get_complete_queryset on ParentModel that returns a broader set of Parents than the default queryset.
The setup above allows me to go to my ChildModelAdmin and select the 'hidden' Parents from the dropdown, but when I try and save it gives me this error:
parent instance with id 2 does not exist.
There must be some queryset the form is using to save the model that isn't overridden, but I can't find what it is.
You can override get_form method like this:
def get_form(self, request, obj, **kwargs):
form = super(<YourModelAdmin>,self).get_form(request, obj, **kwargs)
form.base_fields['<you_field>'] = forms.ModelChoiceField(queryset=<your_queryset>)
return form

Limit the Choices shown from ManyToMany ForeignKey

How do I limit the values returned via the ManyToMany relationship and thus displayed in the <SELECT> field on my form to only show the spots which were created by the currently logged in user?
models.py
class Project(models.Model):
owner = models.ForeignKey(User, editable=False)
...
spots = models.ManyToManyField(to='Spot', blank=True, )
class Spot(models.Model):
owner = models.ForeignKey(User, editable=False)
spot_name = models.CharField(max_length=80, blank=False)
forms.py
from django import forms
from .models import Project, Spot
class ProjectForm(forms.ModelForm):
class Meta:
model = Project
exclude = ('owner', )
class SpotForm(forms.ModelForm):
class Meta:
model = Spot
exclude = ('owner', )
I'm using GenericViews for Update and Create and currently see all of the entries everyone has made into Spots when I'm updating or creating a Project. I want to see only the entries entered by the logged in user. For completeness sake, yes, the project.owner and spot.owner were set to User when they were created.
I've tried def INIT in the forms.py and using limit_choices_to on the manytomany field in the model. Either I did those both wrong or that's not the right way to do it.
thank you!
in your forms.py
class ProjectForm(forms.ModelForm):
class Meta:
model = Project
exclude = ('owner', )
def __init__(self, user_id, *args, **kwargs):
self.fields['spots'] = forms.ModelChoiceField(widget=forms.Select, queryset=Project.objects.filter(owner=user_id))
class SpotForm(forms.ModelForm):
class Meta:
model = Spot
exclude = ('owner', )
def __init__(self, user_id, *args, **kwargs):
self.fields['spot_name'] = forms.ModelChoiceField(widget=forms.Select, queryset=Spot.objects.filter(owner=user_id))
in your views.py
user_id = Project.objects.get(owner=request.user).owner
project_form = ProjectForm(user_id)
spot_form = SpotForm(user_id)
As I mentioned above, Dean's answer was really close, but didn't work for me. Primarily because request is not accessible in the view directly. Maybe it is in older Django versions? I'm on 1.9. Thank you Dean, you got me over the hump!
The gist of what's going on is adding User into the kwargs in the View, passing that to the ModelForm, remove User from the kwargs and use it to filter the Spots before the form is shown.
This is the code that worked for my project:
views.py
class ProjectUpdate(UpdateView):
model = Project
success_url = reverse_lazy('projects-mine')
form_class = ProjectForm
def dispatch(self, *args, **kwargs):
return super(ProjectUpdate, self).dispatch(*args, **kwargs)
def get_form_kwargs(self):
kwargs = super(ProjectUpdate, self).get_form_kwargs()
kwargs.update({'user': self.request.user})
return kwargs
forms.py
class ProjectForm(forms.ModelForm):
class Meta:
model = Project
exclude = ('owner', 'whispir_id')
def __init__(self, *args, **kwargs):
user_id = kwargs.pop('user')
super(ProjectForm, self).__init__(*args, **kwargs)
self.fields['spots'] = forms.ModelMultipleChoiceField(queryset=Spot.objects.filter(owner=user_id))
class SpotForm(forms.ModelForm):
class Meta:
model = Spot
exclude = ('owner', )
def __init__(self, *args, **kwargs):
user_id = kwargs.pop('user')
super(SpotForm, self).__init__(*args, **kwargs)
self.fields['spot_name'] = forms.ModelMultipleChoiceField(queryset=Spot.objects.filter(owner=user_id))

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()