Django. Override save for model - python

Before saving model I'm re-size a picture. But how can I check if new picture added or just description updated, so I can skip rescaling every time the model is saved?
class Model(model.Model):
image=models.ImageField(upload_to='folder')
thumb=models.ImageField(upload_to='folder')
description=models.CharField()
def save(self, *args, **kwargs):
if self.image:
small=rescale_image(self.image,width=100,height=100)
self.image_small=SimpleUploadedFile(name,small_pic)
super(Model, self).save(*args, **kwargs)
I want to rescale only if new image loaded or image updated, but not when description updated.

Some thoughts:
class Model(model.Model):
_image=models.ImageField(upload_to='folder')
thumb=models.ImageField(upload_to='folder')
description=models.CharField()
def set_image(self, val):
self._image = val
self._image_changed = True
# Or put whole logic in here
small = rescale_image(self.image,width=100,height=100)
self.image_small=SimpleUploadedFile(name,small_pic)
def get_image(self):
return self._image
image = property(get_image, set_image)
# this is not needed if small_image is created at set_image
def save(self, *args, **kwargs):
if getattr(self, '_image_changed', True):
small=rescale_image(self.image,width=100,height=100)
self.image_small=SimpleUploadedFile(name,small_pic)
super(Model, self).save(*args, **kwargs)
Not sure if it would play nice with all pseudo-auto django tools (Example: ModelForm, contrib.admin etc).

Check the model's pk field. If it is None, then it is a new object.
class Model(model.Model):
image=models.ImageField(upload_to='folder')
thumb=models.ImageField(upload_to='folder')
description=models.CharField()
def save(self, *args, **kwargs):
if 'form' in kwargs:
form=kwargs['form']
else:
form=None
if self.pk is None and form is not None and 'image' in form.changed_data:
small=rescale_image(self.image,width=100,height=100)
self.image_small=SimpleUploadedFile(name,small_pic)
super(Model, self).save(*args, **kwargs)
Edit: I've added a check for 'image' in form.changed_data. This assumes that you're using the admin site to update your images. You'll also have to override the default save_model method as indicated below.
class ModelAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
obj.save(form=form)

You may supply extra argument for confirming a new image is posted.
Something like:
def save(self, new_image=False, *args, **kwargs):
if new_image:
small=rescale_image(self.image,width=100,height=100)
self.image_small=SimpleUploadedFile(name,small_pic)
super(Model, self).save(*args, **kwargs)
or pass request variable
def save(self, request=False, *args, **kwargs):
if request and request.FILES.get('image',False):
small=rescale_image(self.image,width=100,height=100)
self.image_small=SimpleUploadedFile(name,small_pic)
super(Model, self).save(*args, **kwargs)
I think these wont break your save when called simply.
You may put this in your admin.py so that this work with admin site too (for second of above solutions):
class ModelAdmin(admin.ModelAdmin):
....
def save_model(self, request, obj, form, change):
instance = form.save(commit=False)
instance.save(request=request)
return instance

Query the database for an existing record with the same PK. Compare the file sizes and checksums of the new and existing images to see if they're the same.

What I did to achieve the goal was to make this..
# I added an extra_command argument that defaults to blank
def save(self, extra_command="", *args, **kwargs):
and below the save() method is this..
# override the save method to create an image thumbnail
if self.image and extra_command != "skip creating photo thumbnail":
# your logic here
so when i edit some fields but not editing the image, I put this..
Model.save("skip creating photo thumbnail")
you can replace the "skip creating photo thumbnail" with "im just editing the description" or a more formal text.
Hope this one helps!

In new version it is like this:
def validate(self, attrs):
has_unknown_fields = set(self.initial_data) - set(self.fields.keys())
if has_unknown_fields:
raise serializers.ValidationError("Do not send extra fields")
return attrs

I have found one another simple way to store the data into the database
models.py
class LinkModel(models.Model):
link = models.CharField(max_length=500)
shortLink = models.CharField(max_length=30,unique=True)
In database I have only 2 variables
views.py
class HomeView(TemplateView):
def post(self,request, *args, **kwargs):
form = LinkForm(request.POST)
if form.is_valid():
text = form.cleaned_data['link'] # text for link
dbobj = LinkModel()
dbobj.link = text
self.no = self.gen.generateShortLink() # no for shortLink
dbobj.shortLink = str(self.no)
dbobj.save() # Saving from views.py
In this I have created the instance of model in views.py only and putting/saving data into 2 variables from views only.

Related

Form Problems - Setting Initial Value

I am trying to set the initial value of a field on a form. The field is not part of the model, but when I try and set it to a value the field is blank. From my research it could be because the form is "bound" which makes some sense to me, but in this case the field is not part of the model.
My form:
#Form for editing profile
class CatForm(forms.ModelForm):
pictureid = forms.CharField()
class Meta:
model = Cat
fields = ['name']
def __init__(self, *args, **kwargs):
picid = kwargs.pop("pictureid")
print(picid)
super(CatForm, self).__init__(*args, **kwargs)
self.fields['pictureid'] = forms.CharField(initial=picid, required=False)
The model:
class Cat(models.Model):
name = models.CharField(max_length=34,null=False)
From the view it is called like this:
catform = CatForm(request.POST, pictureid=instance.id)
I was expecting it to set the field to the value of the initial attribute, but it doesn't. I have tried testing it by directly adding a string, but doesn't set.
This is what seems to be working for me:
class CatForm(forms.ModelForm):
class Meta:
model = Cat
fields = ['name']
def __init__(self, *args, **kwargs):
picid = kwargs.pop("pictureid")
super(CatForm, self).__init__(*args, **kwargs)
self.fields['pictureid'] = forms.CharField(initial=picid)
I also needed to drop the "request.POST" from the call to this when initiating the form.
If you want to render the pictureid in GET request, then you can try like this:
catform = CatForm(initial={'pictureid': instance.id})
For GET request, you don't need to override the __init__ method.
But, if you want to use the Catform in POST request, to use the value of pictureid somewhere else(lets say in save method), then you will need to override __init__ method here.
class CatForm(forms.ModelForm):
pictureid = forms.CharField()
class Meta:
model = Cat
fields = ['name']
def __init__(self, *args, **kwargs):
picid = kwargs.pop("pictureid")
print(picid)
super(CatForm, self).__init__(*args, **kwargs)
self.pictureid = picid
def save(self, *args, **kwargs):
print(self.pictureid) # if you want to use it in save method
return super().save(*args, **kwargs)

How do you save this form data?

Here is my forms.py:
class MatchingForm(forms.Form):
def __init__(self, *args, **kwargs):
super(MatchingForm, self).__init__(*args, **kwargs)
for matchq in MatchQuestions.objects.all():
self.fields[matchq.question] = forms.ModelChoiceField(queryset=Choice.objects.filter(question=matchq.id))
self.fields[matchq.howImportant] = forms.ChoiceField(choices=((1,"Very Important"),(2,"Not Important"),))
As you can see, this form iterates through data in my database. How can I iterate through these dynamic form fields in my view so that it saves each to each? thank you
The basic idea is to overwrite the save method of your form, and iterate cleaned_data
class MatchingForm(forms.Form):
[...]
def save(self):
for question, howImportant in self.cleaned_data.items():
obj, created = MatchQuestions.objects.get_or_create(question=question,
howImportant=howImportant)
PD: This is an example, because I don't know your data models.

How to change input fields while overriding a django save() method

Suppose I had this following code:
class MyModel(models.Model):
...
...
def save(self, *args, **kwargs):
# pre-save edits can go here...
super(MyModel, self).save(*args, **kwargs)
When I create and save a model, MyModel(blah, blah, blah), there is a possibility that
one of the input fields is "None". In the overridden save method, the goal is to check for if a field is none, and if one is, change it to some other default value.
Are the input fields in args or kwargs? And is overriding save() even the proper way to do this?
I was thinking something like this:
def save(self, *args, **kwargs):
if 'username' in args and args['username'] is None:
args['username'] = some_default_value
super(MyModel, self).save(*args, **kwargs)
So where are the input params? args* or **kwargs, thank you.
I think it's better a pre_save signal to see if a input value is None:
from django.db import models
from django.db.models.signals import pre_save
from django.dispatch import receiver
class MyModel(models.Model):
field1 = models.TextField()
field2 = models.IntegerField()
#receiver(pre_save, sender=MyModel)
def mymodel_save_handler(sender, instance, *args, **kwargs):
if instance.field1 is None or instance.field1 == "":
instance.field1 = default_value
If you prefer override save method you can access to the model fields with self
def save(self, *args, **kwargs):
if self.username is None:
self.username = some_default_value
super(MyModel, self).save(*args, **kwargs)

ModelChoiceField initial value in change form

First the code:
class CommentForm(forms.ModelForm):
categories = forms.ModelChoiceField(queryset = Category.objects.all(), required = False)
class CommentAdmin(admin.ModelAdmin):
form = CommentForm
When I'm editing my comment I'd like it categories field have the initial value of what's been selected when I saved it for the last time. How do I do that?
def get_form(self, *args, **kwargs):
f = super(CommentAdmin, self).get_form(*args, **kwargs)
f.base_fields['categories'].initial = 1
return f
This code placed in CommentAdmin did the trick...
EDIT:
def __init__(self, *args, **kwargs):
super(CommentForm, self).__init__(*args, **kwargs)
self.fields['categories'].initial = self.instance.object_id
Or this code placed in CommentForm
You want to have the current model value selected by default in the generated form? If that's the case I think what you are looking for in your view is
form = CommentForm(instance = commentinstance)
Where commentinstance is the instance that you are editing.
(This would be form = CommentForm(request.POST, instance = commentinstance) in case of a POST request)
EDIT:
If you want to do this in the form, you can just provide the instance argument from __init__, like so:
def __init__(self, *args, **kwargs):
instance = kwargs.pop('instance', YOUR_DEFAULT_INSTANCE)
super(CommentForm, self).__init__(instance = instance, *args, **kwargs)
That even leaves the default instance if you do provide one from your view.
I guess there are a few ways to solve this.
Here is how I done before:
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
if 'ref' in kwargs:
ref = kwargs['ref']
item = MyModel.objects.get(pk=ref)
kwargs['instance'] = item
super(MyForm, self).__init__(*args, **kwargs)
class Meta:
model = MyModel
The important part is to put your populated model object into the keyword variable instance.

Accessing parent model instance from modelform of admin inline

I'm using a TabularInline in Django's admin, configured to show one extra blank form.
class MyChildInline(admin.TabularInline):
model = MyChildModel
form = MyChildInlineForm
extra = 1
The model looks like MyParentModel->MyChildModel->MyInlineForm.
I'm using a custom form so I can dynamically lookup values and populate choices in a field. e.g.
class MyChildInlineForm(ModelForm):
my_choice_field = forms.ChoiceField()
def __init__(self, *args, **kwargs):
super(MyChildInlineForm, self).__init__(*args, **kwargs)
# Lookup ID of parent model.
parent_id = None
if "parent_id" in kwargs:
parent_id = kwargs.pop("parent_id")
elif self.instance.parent_id:
parent_id = self.instance.parent_id
elif self.is_bound:
parent_id = self.data['%s-parent'% self.prefix]
if parent_id:
parent = MyParentModel.objects.get(id=parent_id)
if rev:
qs = parent.get_choices()
self.fields['my_choice_field'].choices = [(r.name,r.value) for r in qs]
This works fine for the inline records bound to an actual record, but for the extra blank form, it doesn't display any values in my choice field, since it doesn't have any record id and there can't lookup the associated MyParentModel record.
I've inspected all the values I could find (args, kwargs, self.data, self.instance, etc) but I can't find any way to access the parent object the tabular inline is bound to. Is there any way to do this?
Update: As of Django 1.9, there is a def get_form_kwargs(self, index) method in the BaseFormSet class. Hence, overriding that passes the data to the form.
This would be the Python 3 / Django 1.9+ version:
class MyFormSet(BaseInlineFormSet):
def get_form_kwargs(self, index):
kwargs = super().get_form_kwargs(index)
kwargs['parent_object'] = self.instance
return kwargs
class MyForm(forms.ModelForm):
def __init__(self, *args, parent_object, **kwargs):
self.parent_object = parent_object
super(MyForm, self).__init__(*args, **kwargs)
class MyChildInline(admin.TabularInline):
formset = MyFormSet
form = MyForm
For Django 1.8 and below:
To pass a value of a formset to the individual forms, you'd have to see how they are constructed. An editor/IDE with "jump to definition" really helps here to dive into the ModelAdmin code, and learn about the inlineformset_factory and it's BaseInlineFormSet class.
From there you'll find that the form is constructed in _construct_form() and you can override that to pass extra parameters. It will likely look something like this:
class MyFormSet(BaseInlineFormSet):
def _construct_form(self, i, **kwargs):
kwargs['parent_object'] = self.instance
return super(MyFormSet, self)._construct_form(i, **kwargs)
#property
def empty_form(self):
form = self.form(
auto_id=self.auto_id,
prefix=self.add_prefix('__prefix__'),
empty_permitted=True,
parent_object=self.instance,
)
self.add_fields(form, None)
return form
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.parent_object = kwargs.pop('parent_object', None)
super(MyForm, self).__init__(*args, **kwargs)
class MyChildInline(admin.TabularInline):
formset = MyFormSet
form = MyForm
Yes, this involves a private _construct_form function.
update Note: This doesn't cover the empty_form, hence your form code needs to accept the parameters optionally.
I'm using Django 1.10 and it works for me:
Create a FormSet and put the parent object into kwargs:
class MyFormSet(BaseInlineFormSet):
def get_form_kwargs(self, index):
kwargs = super(MyFormSet, self).get_form_kwargs(index)
kwargs.update({'parent': self.instance})
return kwargs
Create a Form and pop an atribute before super called
class MyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
parent = kwargs.pop('parent')
super(MyForm, self).__init__(*args, **kwargs)
# do whatever you need to with parent
Put that in the inline admin:
class MyModelInline(admin.StackedInline):
model = MyModel
fields = ('my_fields', )
form = MyFrom
formset = MyFormSet
AdminModel has some methods like get_formsets. It receives an object and returns a bunch of formsets. I think you can add some info about parent object to that formset classes and use it later in formset's __init__
Expanding on ilvar's answer a bit, If the form field of interest is constructed from a model field, you can use the following construction to apply custom behavior to it:
class MyChildInline(admin.TabularInline):
model = MyChildModel
extra = 1
def get_formset(self, request, parent=None, **kwargs):
def formfield_callback(db_field):
"""
Constructor of the formfield given the model field.
"""
formfield = self.formfield_for_dbfield(db_field, request=request)
if db_field.name == 'my_choice_field' and parent is not None:
formfield.choices = parent.get_choices()
return formfield
return super(MyChildInline, self).get_formset(
request, obj=obj, formfield_callback=formfield_callback, **kwargs)
return result

Categories