Django method update_or_create troubles to increment value - python

I've got a Django models:
class Item_received_log(models.Model):
name = models.CharField(max_length=250)
quantity = models.FloatField(default=1)
class Inventory (models.Model):
name = models.CharField(max_length=250)
quantity = models.FloatField(default=1)
I would like to update Inventory.quantity each time new item to Item_received_log is posted with matching name. I am not sure if it is right but I've decided to override save method of Item_received_log class so it updates Inventory list upon saving:
def save(self, *args, **kwargs):
obj, created = Inventory.objects.update_or_create(
name=self.name,
defaults = {'quantity':(quantity + self.quantity)})
super(Item_received_log, self).save(*args, **kwargs)
And in returns:
NameError at /admin/accountable_persons/item_received_log/17/change/
global name 'quantity' is not defined
How can I resolve my issue or come up with better solution?

Would have been a lot easier if we could simply throw in an F() expression into the default part of update_or_create to do all the magic, but the issue requesting this feature is still open.
You can, however, use a more verbose approach for now:
from django.db.models import F
def save(self, *args, **kwargs):
obj, created = Inventory.objects.get_or_create(name=self.name)
obj.quantity = F('quantity') + self.quantity
obj.save()
super(Item_received_log, self).save(*args, **kwargs)

Related

Override save model Django save method with return

I have this function, I want to make it a method of a Message model class.
def save_message_to_db(message, message_id):
mex = Message(
message_id=message_id,
subject=message.subject,
sender=message.sender.address,
has_attachments=message.has_attachments,
sent_date=message.sent,
received_date=message.received
)
mex.save()
return mex
I've tried various ways, but still get errors. I need to return, expecially the id of the object saved.
Update
#staticmethod
def save_mex(message, message_id):
mex = Message(
message_id=message_id,
subject=message.subject,
sender=message.sender.address,
has_attachments=message.has_attachments,
sent_date=message.sent,
received_date=message.received
)
mex.save()
return mex
this is the only way I made it work, but this is a work around...
I get the errors in the Pyacharm IDE, I can not understand how to use the super() in this situation, because I want to pass an object and treat it in this method, not args and kwargs.
It should be as simple as this. Simply override the save method of your model and return the instance after the super call.
class YourModel(models.Model):
name = models.CharField(max_length=20)
def save(self, *args, **kwargs):
super(YourModel, self).save(*args, **kwargs)
return self
your_model_saved_instance = YourModel(name='Edoardo').save()
You can even make a base model class with this feature and use it in every model you want.
class BaseModel(models.Model):
class Meta:
abstract = True
def save(self, *args, **kwargs):
super(BaseModel, self).save(*args, **kwargs)
return self
class YourModel(BaseModel):
name = models.CharField(max_length=20)
your_model_saved_instance = YourModel(name='Edoardo').save()

Add a date or a number at the end of a slug

i'm trying to add a number or publish date at the end of a slug, off the page mode, if the slug already exist.
I found a possible solution with RoutablePageMixin but not quite sure how to approach it as it changes the URL not the slug itself.
Here is what i got so far but it seams that is not working...
class BlogPage(HeadlessPreviewMixin, Page):
#...
def full_clean(self, *args, **kwargs):
# first call the built-in cleanups (including default slug generation)
super(BlogPage, self).full_clean(*args, **kwargs)
# now make your additional modifications
if not self.slug.startswith('awesome'):
self.slug = "awesome-%s" % self.slug
I believe what you're trying to achieve can be done on the .save() of the model.
models.py
import datetime as dt
class BlogPage(...):
def save(self, **kwargs):
now = dt.datetime.now()
if self.slug:
self.slug = f"{self.slug}-{now.isoformat()}"
super().save(self, **kwargs)
OR you can do this...
views.py
class BlogPage(RoutablePageMixin, Page):
# Multiple routes!
#route(r'^$')
#route(r'^year/(\d+)/$')
def events_for_year(self, request, year=None):
...

How to get ForeignKey field value during new model creation?

I'm looking for the most efficient way to implement this kind of mechanism in Django model.
Let's assume a situation, where there are 2 very simple models:
class FKModel(models.Model):
value = BooleanField()
class AModel(models.Model):
fk = models.ForeignKey(FKModel)
a_value = models.CharField(max_length=150)
def clean(self, *args, **kwargs):
# the line below is incorrect
if not self.fk.value: # <--- how to do this in a proper way?
raise ValidationError('FKModel value is False')
super(AModel, self).clean(*args, **kwargs)
def save(self, *args, **kwargs):
self.full_clean()
super(AModel, self).save(*args, **kwargs)
I know, that I can do somethink like FKModel.objects.all()/.get(), but I don't think it is the best solution (as it requires additional requests to database).
I am not sure what you try to do in your clean() method, but I assume you are trying to constrain a not null condition for the foreign key. All fields are not null constrained by default, and you have to set null=False and blank=False if you want the field to accept nulls:
class AModel(models.Model):
fk = models.ForeignKey(FKModel, null=True, blank=True)
a_value = models.CharField(max_length=150)
If you want to constrain a not null condition for a field by hand, you should do it like this:
class FKModel(models.Model):
value = BooleanField()
class AModel(models.Model):
fk = models.ForeignKey(FKModel)
a_value = models.CharField(max_length=150)
def clean(self, *args, **kwargs):
# the line below is correct
if self.fk is None: # <--- this is the proper way?
raise ValidationError('FKModel value is False')
super(AModel, self).clean(*args, **kwargs)
def save(self, *args, **kwargs):
self.full_clean()
super(AModel, self).save(*args, **kwargs)
For retrieving database records and its related records, you use prefetch_related, and you get your record and its related records in one single database hit:
AModel.objects.all().prefetch_related('fk')

Overriding save method to create second auto incrementing field in Django

I am trying to override the save method on a model in order to generate a unique, second auto-incrementing id.
I create my class and override the save() method, but for some reason it is erroring out with the following error:
TypeError: %d format: a number is required, not NoneType
Here's the code:
class Person(models.Model):
target = models.OneToOneField(Target)
person = models.OneToOneField(User)
gender = models.CharField(max_length=1)
gender_name = models.CharField(max_length=100)
person_id = models.CharField(max_length=100)
def save(self, *args, **kwargs):
self.person_id = "%07d" % self.id
super(Person, self).save(*args, **kwargs)
Is it because I didn't pass an id parameter and it hasn't saved yet? Is there anyway to generate a value from the id?
Safest and easiest way to achieve what you want is to use a post_save signal because it is fired right after save is called, but before the transaction is committed to the database.
from django.dispatch import receiver
from django.db.models.signals import post_save
#receiver(post_save, sender=Person)
def set_person_id(sender, instance, created, **kwargs):
if created:
instance.person_id = "%07d" % instance.id
instance.save()
Yes, self.id will be Nonein some cases, and then the assignment will fail.
However you cannot just the assignment and the call to super, as suggested in the comments, because then you wouldn't be persisting the assignment to the database layer.
You need to check whether the model has an id and then proceed differently:
def save(self, *args, **kwargs):
if not self.id: # Upon instance creation
super(Person, self).save(*args, **kwargs) # Acquire an ID
self.person_id = "%07d" % self.id # Set the person_id
return super(Person, self).save(*args, **kwargs)
This issues two save operations to the database. You will want to wrap them in a transaction to make sure your database receives these two fields simultaneously.
from django.db import IntegrityError, transaction
class Person(models.Model):
target = models.OneToOneField(Target)
person = models.OneToOneField(User)
gender = models.CharField(max_length=1)
gender_name = models.CharField(max_length=100)
person_id = models.CharField(max_length=100)
def create_person_id(self):
if not self.id: # Upon instance creation
super(Person, self).save(*args, **kwargs) # Acquire an ID
self.person_id = "%07d" % self.id
def save(self, *args, **kwargs):
try:
with transaction.atomic():
self.create_person_id
return super(Person, self).save(*args,**kwargs)
except IntegrityError:
raise # or deal with the error
I agree that signals might be the better option, if not, try using pk instead of id.
class Person(models.Model):
# [ . . . ]
def save(self, *args, **kwargs):
self.person_id = "%07d" % self.pk
super(Person, self).save(*args, **kwargs)

TemplateView with forms - 'unicode' object has no attribute 'get'

Am having a challenge i hope you can help me over come.
Am building a django driven application for movie ticket bookings and coming up short on the forms.
When a user clicks on a particular movie, i want to render a page that has a form where the user can choose options for his/her ticket like number of tickets, seat number, date etc.
However, my form returns all movies in the database.
i want to be able to return the ONLY the movie the user has clicked on, seeing that this view already returns a particular movie clicked on by a user. How can i do this?
My current method gives me an exception error 'unicode' object has no attribute 'get'
In my forms.py i have this
class MoviePaymentsForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MoviePaymentsForm, self).__init__(*args, **kwargs)
movie = forms.ModelChoiceField(queryset=Movie.objects.get(slug=args[0]))
and in my views.py i have this
class SpecificMovieTemplateView(TemplateView):
model = Movie
template_name = 'movie.html'
def get_context_data(self, *args, **kwargs):
context = super(SpecificMovieTemplateView, self).get_context_data(**kwargs)
context['movie'] = Movie.objects.get(slug=kwargs['movieslug'])
print 'Movie ID is ==> ' + str(context['movie'].id)
context['form_movie'] = MoviePaymentsForm(kwargs['movieslug'])
return context
in my models.py i have this
class MoviePayments(TimeStampedModel):
uuid_placement = shortuuid.encode(uuid.uuid4())
short_uuid = uuid_placement[:8]
reference_id = models.CharField(max_length=8, blank=True, unique=True,
default="%s" % str(short_uuid))
movie = models.ForeignKey(Movie)
ticket = models.ForeignKey(Ticket)
quantity = models.IntegerField()
date = models.ForeignKey(MovieShowDate)
time = models.ForeignKey(MovieShowTimes)
paid_for = models.BooleanField(default=False, blank=False)
mm_transaction_id = models.CharField(max_length=100, blank=True)
I finally figured it out. Like Bogdan pointed out above, i needed to pass the slug field as an argument in the init method, and use filter on the queryset to return that particular movie like so
class MoviePaymentsForm(forms.ModelForm):
def __init__(self, slug, *args, **kwargs):
super(MoviePaymentsForm, self).__init__(*args, **kwargs)
self.fields['movie'].queryset = Movie.objects.filter(slug=slug)
The problem is you are passing the movie_slug as first parameter to the form:
context['form_movie'] = MoviePaymentsForm(kwargs['movieslug']) and first parameter to the form is the data dictionary. Modify the form like this:
class MoviePaymentsForm(forms.ModelForm):
def __init__(self, slug=None, *args, **kwargs):
super(MoviePaymentsForm, self).__init__(*args, **kwargs)
movie = forms.ModelChoiceField(queryset=Movie.objects.get(slug=slug))
Or remove the argument from the args list like: slug = args.pop(0)

Categories