Django save override not working - python

I am trying to override django's default model save method like this:
def save(self, *args, **kwargs):
if not self.pk:
ct = Cart()
ct.save()
dct = Dummycart()
dct.save()
self.cart=ct
self.dummycart = dct
self.password = make_password(self.password)
super(User,self).save(self, *args, **kwargs)
but this is creating new object every time i update thid model,it's strange behaviour

You should call super like this:
super(User, self).save(*args, **kwargs)

Related

Django passing arguments through save methods (models)

i have models in django like this:
class Client(models.Model):
type = (choices)
class Bill(models.Model):
client = models.ForeignKey(Client)
class Detail(models.Model):
total = models.MoneyField() # i used currency package
bill = models.ForeignKey(Bill)
Detail class contains sales detail for the Bill, i already made a transaction to save bill and details at the same time in Bill.save() method but i want to pass Client.type from Bill.save() to Detail.Save(), i want something like that
def save(self, *args, **kwargs): #this is Bill save method
client = self.Client
transaction.atomic:
super(Bill, self).save(*args, **kwargs)
for detail in self.details
detail.save(client)
def save(self, *args, **kwargs): #this is Detail save method
self.pricing(client)
super(Detail, self).save(*args, **kwargs)
def pricing(self, client):
if client.type = 'value1':
self.total = self.total - (self.total*5/100)
elif client.type = 'value2':
self.total = self.total - (self.total*7/100)
else:
self.total = self.total - (self.total*10/100)
i don't know how passing arguments works on python and Django, what is the cleanest solution to solve this problem? in short i want the bill.save method to pick the client.type value and passe it through detail.save to calculate total with cases.
Thanks
You can pass the parameter in kwargs in save method try the following code to pass the client variable (detail.save(client=client)) in another save method and access this variable by using client = kwargs.get('client')
def save(self, *args, **kwargs): #this is Bill save method
client = self.Client
transaction.atomic:
super(Bill, self).save(*args, **kwargs)
for detail in self.details
detail.save(client=client)
def save(self, *args, **kwargs): #this is Detail save method
client = kwargs.get('client')
self.pricing(client)
super(Detail, self).save(*args, **kwargs)
May be this helps you..
In your case, you can simple use self property:
def save(self, *args, **kwargs): #this is Detail save method
self.pricing(self.bill.client)
# ^^^^^^^^^^^
super(Detail, self).save(*args, **kwargs)

How to add an initial/default value using Django Filters?

How can I add an initial/default value when using Django Filters?
For example, something like this initial=False
class UserFilter(django_filters.FilterSet):
archive = django_filters.BooleanFilter(initial=False)
class Meta:
model = User
fields = ['archive']
I've tired to override the __init__ but this does not appear to work.
You can try overriding the __init__ method of UserFilter:
def __init__(self, *args, **kwargs):
super(UserFilter, self).__init__(*args, **kwargs)
self.form.initial['archive'] = False
For DRF you can try override __init__:
def __init__(self, *args, **kwargs):
kwargs['data']._mutable = True
if 'archive' not in kwargs['data']:
kwargs['data']['archive'] = False
kwargs['data']._mutable = False
super(UserFilter, self).__init__(*args, **kwargs)
But you should read django-filter.readthedocs.io...using-initial-values-as-defaults
I had a similar problem in that I wanted to preserve the users selections even after they had navigated away from the page. I implemented my solution in the Filter View as follows...
class BucketLookupView(UserPassesTestMixin,SingleTableMixin,FilterView):
template_name = "finance/planning-bucket-lookup.html"
model = finance_models.SalesOrderBucket
filterset_class = finance_filters.BucketFilter
table_class = finance_tables.BucketTable
def get_filterset_kwargs(self,*args):
kwargs = super().get_filterset_kwargs(*args)
if kwargs['data']:
bucket_filter_data = kwargs['data']
self.request.session['bucket_filter_data']= bucket_filter_data
else:
if 'bucket_filter_data' in self.request.session.keys():
kwargs['data']=self.request.session['bucket_filter_data']
return kwargs
Here is an approximate code snippet I used:
def list(self, request, *args, **kwargs):
filters = QueryDict('is_archive=true', mutable=True)
filters.update(request.GET)
You build a new QueryDict as request.GET is immutable. Not that efficient, but looks pretty to me.
Here is a remix of #IlyaPetukhov's answer that I find easier to read:
def __init__(self, data=None, *args, **kwargs):
if data is not None:
data = data.copy()
data.setdefault("archive", False)
super(UserFilter, self).__init__(data, *args, **kwargs)
If you're uncomfortable with data = data.copy(), you can replace it with data._mutable = True; just know that it's not officially supported by Django.
form.cleaned_data is using to add filters to the queryset and no initial value is set for cleaned data.
The solution could be to override form property:
#property
def form(self) -> ModelForm:
form = super().form
form.data = form.data.copy()
form.data.setdefault("archive", False)
return form

DRF caching is not working while using detail serializer different than default

I'm using django rest framework with DetailSerializerMixin from drf-extensions like this:
class MyClassViewSet(DetailSerializerMixin, viewsets.ReadOnlyModelViewSet):
serializer_class = MyClassSerializer
serializer_detail_class = MyClassDetailSerializer
#cache_response(60*60*12, key_func=CacheKeyConstructor())
def list(self, *args, **kwargs):
response = super(MyClassViewSet, self).list(*args, **kwargs)
# adding some data here
return response
#cache_response(60*60*12, key_func=CacheDetailKeyConstructor())
def retrieve(self, *args, **kwargs):
response = super(MyClassViewSet, self).retrieve(*args, **kwargs)
# adding some data here
return response
Everything works as it should when I'm not using DetailSerializer - it caches the data from basic serializer and it works perfectly, but when I change it to use my DetailSerializer it is not caching. I have tried not to use DetailSerializerMixin and use get_serializer() method instead and replace it there but it has the same effect. I need list data to have only few parameters and detail to have it all so it's a big waste if I will need to add all this stuff to basic serializer. Any suggestions?
#Edit: Added KeyConstructors:
class CacheKeyConstructor(DefaultKeyConstructor):
def __init__(self, *args, **kwargs):
super(CacheKeyConstructor, self).__init__(*args, **kwargs)
self.bits['all_query_params'] = bits.QueryParamsKeyBit()
self.bits['pagination'] = bits.PaginationKeyBit()
self.bits['list_sql_query'] = bits.ListSqlQueryKeyBit()
self.bits['format'] = bits.FormatKeyBit()
class CacheDetailKeyConstructor(DefaultKeyConstructor):
def __init__(self, *args, **kwargs):
super(CacheDetailKeyConstructor, self).__init__(*args, **kwargs)
self.bits['all_query_params'] = bits.QueryParamsKeyBit()
self.bits['retrieve_sql_query'] = bits.RetrieveSqlQueryKeyBit()
self.bits['format'] = bits.FormatKeyBit()
self.bits['kwargs'] = bits.KwargsKeyBit()
If your viewset doesn't have pagination or filtering then you should be able to implement something like this:
class MyClassViewSet(DetailSerializerMixin,
CacheResponseMixin,
viewsets.ReadOnlyModelViewSet)
serializer_class = MyClassSerializer
serializer_detail_class = MyClassDetailSerializer
Their default implementation is to keep 5 minutes cache, but this would help us to narrow down the issue.

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.

Django. Override save for model

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.

Categories