Override save in Django causing infinite recursion error - python

(Django 2.0, Python 3.6, Django Rest Framework 3.8)
I'm trying to override Django's save() method to post multiple instances when a single instance is created. I have a loop that changes the unique_id which I have saved as a randomly generated string, and the datetime value which is updated through another function called onDay().
My thinking was, that if I changed the unique_id each time I looped around, Django would save the instance as a new instance in the database. But, I keep getting back an infinite recursion error when I run it though. When I checked it with pdb.set_trace(), everything does what it's supposed to until I hit the save() value in the for loop. Once that happens, I just get taken back to the line if self.recurrent_type == "WEEKLY":.
I've used super() in a similar way (without looping) to override the save() function for a separate model, and it worked as expected. I think there's just something I'm misunderstanding about the super() function.
Here is what I have so far:
Overriding save()
def save(self, *args, **kwargs):
if not self.pk: # if there is not yet a pk for it
# import pdb; pdb.set_trace()
if self.recurrent_type == "WEEKLY":
LIST_OF_DAYS = self.days_if_recurring["days"]
HOW_MANY_DAYS_FOR_ONE_WEEK = len(LIST_OF_DAYS)
REPEATS = HOW_MANY_DAYS_FOR_ONE_WEEK * self.number_of_times_recurring
RESET_COUNTER = 0
for i in range(REPEATS):
self.id = ''.join(random.choices(string.ascii_letters, k=30))
self.calendarydays = onDay(self.calendarydays, LIST_OF_DAYS[RESET_COUNTER])
if RESET_COUNTER == HOW_MANY_DAYS_FOR_ONE_WEEK - 1:
RESET_COUNTER = 0
self.save()
else:
self.id = ''.join(random.choices(string.ascii_letters, k=30))
self.save()
return super(Bookings, self).save(*args, **kwargs)
onDay()
def onDay(date, day): # this function finds next day of week, and skips ahead one week if today's time has already passed
utc = pytz.UTC
check_right_now = utc.localize(datetime.datetime.now())
if check_right_now > date:
forward_day = date + datetime.timedelta(days=(day - date.weekday() + 7) % 7) + datetime.timedelta(days=7)
else:
forward_day = date + datetime.timedelta(days=(day - date.weekday() + 7) % 7)
return forward_day
As always, any help is greatly appreciated.

You should call super(Bookings, self).save(*args, **kwargs) instead of self.save(). The super save will call django's actual model save which is what you want. Calling self.save() will just call your overridden save which doesn't do anything in the database. But yeah what #AamirAdnan said should fix your problem.

Related

Django post save signal not updating the database

Im not getting why this signal is not working. This same code worked once but after that i deleted the objects from admin and ran it again and it stopped working.
#receiver(post_save, sender=FinancePending)
def calcualate_FinancePending(sender, instance, created, **kwargs):
amount_paid = FinancePending.objects.values_list('amountPaid', flat=True)
amount_paid = list(amount_paid)
total_amount = FinancePending.objects.values_list('TotalAmount', flat=True)
total_amount = list(total_amount)
# total - paid
TotalFee = [int(s.replace(',', '')) for s in total_amount]
AmountPaid = [int(s.replace(',', '')) for s in amount_paid]
finance_pending = FinancePending.objects.all()
i = 1
while i <= len(TotalFee):
amount_pending = TotalFee[i-1] - AmountPaid[i-1]
amountpending = FinancePending.objects.filter(invoiceNumber=i)
amountpending.AmountPending = amount_pending
i = 1 + i
You did not call save() method, that is why it is not saving. But I do not think it is a optimized implementation from django's prespective. You can simply try like this using update():
from django.db.models import F
#receiver(post_save, sender=FinancePending)
def calcualate_FinancePending(sender, instance, created, **kwargs):
FinancePending.objects.update(AmountPending=F('TotalAmount')-F('amountPaid'))
Also, it does not make sense to update each and every object one instance of FinancePending is created. Probably you should only update the object which was created. Like this:
#receiver(post_save, sender=FinancePending)
def calcualate_FinancePending(sender, instance, created, **kwargs):
instance.AmountPending=instance.TotalAmount-instance.amountPaid
instance.save()
Finally, please follow pep8 style guide when naming your attributes and function names.
Because signal post_save only triggered after call save() method. You should use post_delete instead.

Django find item in queryset and get next

I'm trying to take an object, look up a queryset, find the item in that queryset, and find the next one.
#property
def next_object_url(self):
contacts = Model.objects.filter(owner=self.owner).order_by('-date')
place_in_query = list(contacts.values_list('id', flat=True)).index(self.id)
next_contact = contacts[place_in_query + 1]
When I add this to the model and run it, here's what I get for each variable for one instance.
CURRENT = Current Object
NEXT = Next Object
contacts.count = 1114
self.id = 3533 #This is CURRENT.id
place_in_query = 36
contacts[place_in_query] = NEXT
next_contact = CURRENT
What am i missing / what dumb mistake am i making?
In your function, contacts is a QuerySet. The actual objets are not fetched in the line:
contacts = Model.objects.filter(owner=self.owner).order_by('-date')
because you don’t use a function like list(), you don’t iterate the QuerySet yet... It is evaluated later. This is probably the reason of your problem.
Since you need to search an ID in the list of contacts and the find the next object in that list, I think there is no way but fetch all the contact and use a classic Python loop to find yours objects.
#property
def next_object_url(self):
contacts = list(Model.objects.filter(owner=self.owner).order_by('-date').all())
for curr_contact, next_contact in zip(contacts[:-1], contacts[1:]):
if curr_contact.id == self.id:
return next_contact
else:
# not found
raise ContactNotFoundError(self.id)
Another solution would be to change your database model in order to add a notion of previous/next contact at database level…

Django receiver check if first create

The idea of the code below should be that it only fires if the field verification_pin is empty i.e. on a new record. However, it seems that every time I save the model it generates a new pin ignoring if instance.verification_pin is None statement, why, what have I missed?
#receiver(pre_save, sender=CompanyUser)
def my_callback(sender, instance, *args, **kwargs):
if instance.verification_pin is None:
instance.verification_pin = instance.generate_pin()
instance.is_active = False
instance.send_verification_pin()
Model:
class CompanyUser(User):
verification_pin = models.IntegerField(max_length=4, null=True)
objects = UserManager()
def generate_pin(self):
"""
Returns a random four digit pin.
"""
return random.randint(999, 9999)
def send_verification_pin(self):
self.email_user(
subject="Test",
message="Your pin: %s" % self.verification_pin,
from_email=settings.DEFAULT_FROM_EMAIL
)
You can set the default value for a field to a callable object.
Or, you might try using a post_save handler instead, where you can check if created is True. Also, it might be helpful to check the value in verification_pin after saving, to see if it really got set or not.

How do I paginate WeekArchiveView?

In continuation of my struggle with WeekArchiveView, how do I paginate it by week?
All I want is:
to know if there is next / previous week available;
in case there is, provide a link in the template.
I'd like it to also skip empty weeks.
The source shows get_next_day / get_prev_day and get_next_month / get_prev_month are available, but nothing for weeks.
That is definitely interesting. Sure enough MonthMixin includes get_next_month/get_prev_month methods, and DayMixin includes get_next_day/get_prev_day methods. However, both YearMixin and WeekMixin have no functional equivalent in their definitions. Seems like a bit of an oversight on the Django team's part.
I think your best bet is to subclass either WeekArchiveView or BaseWeekArchiveView (if you may eventually want to change up the response format and don't want to have to re-implement your methods) and add your own get_next_week/get_prev_week methods. Then have your view inherit from your subclass instead. A simple modification of DayMixins methods should be sufficient.
def get_next_week(self, date):
"""
Get the next valid week.
"""
next = date + datetime.timedelta(days=7)
return _get_next_prev_month(self, next, is_previous=False, use_first_day=False)
def get_previous_week(self, date):
"""
Get the previous valid week.
"""
prev = date - datetime.timedelta(days=7)
return _get_next_prev_month(self, prev, is_previous=True, use_first_day=False)
Taking chrisdpratt's code as basis, I created a class that provides the template with next_week and previous_week:
class BetterWeekArchiveView(WeekArchiveView):
def get_next_week(self, date):
"""
Get the next valid week.
"""
next = date + timedelta(days=7)
return _get_next_prev_month(self, next, is_previous=False, use_first_day=False)
def get_previous_week(self, date):
"""
Get the previous valid week.
"""
prev = date - timedelta(days=7)
return _get_next_prev_month(self, prev, is_previous=True, use_first_day=False)
def get_dated_items(self):
"""
Return (date_list, items, extra_context) for this request.
Inject next_week and previous_week into extra_context.
"""
result = super(BetterWeekArchiveView, self).get_dated_items()
extra_context = result[2]
date = extra_context['week']
extra_context.update({
'next_week': self.get_next_week(date),
'previous_week': self.get_previous_week(date),
})
return result
This works perfect.

Django : Timestamp string custom field

I'm trying to create a custom timestamp field.
class TimestampKey(models.CharField):
__metaclass__ = models.SubfieldBase
def __init__(self, *args, **kwargs):
import time
kwargs['unique'] = True
kwargs['max_length'] = 20
kwargs['auto_created'] = True
kwargs['editable']=False
super(TimestampKey, self).__init__(*args, **kwargs)
def to_python(self, value) :
return value
def get_db_prep_value(self, value) :
try:
import time
t = time.localtime()
value = reduce(lambda a,b:str(a)+str(b),t)
except ValueError:
value = {}
return value
class Table1(models.Model):
f = TimestampKey(primary_key=True)
n = ....
It stores the value with appropriate timestamp in the db. But it doesnt populate the field 'f' in the object.
Eg:
t1 = Table1(n="some value")
t1.f -> blank
t1.save()
t1.f -> blank.
This is the problem. Am I missing something so that it doesnt populate the filed?
Please shed some light on this.
Thanks.
Is it wise to use a timestamp as your primary key? If your database uses ISO 8601 or really any time format in which second is the smallest time interval... Well, anyway, my point is that you have no guarantee, especially if this is going to be a web-facing application that two entries are going to resolve within the minimum time interval. That is, if the smallest time interval is a second, as in ISO 8601, if you get two requests to save in the same second, you're going to get an error condition. Why not stick to automatically incrementing integer keys and just make the timestamp its own field?
The get_db_prep_value method only prepares a value for the database, but doesn't send the prepared value back to the Python object in any way. For that you would need the pre_save method, I think.
Fortunately, there's already an "auto_now" option on DateField and DateTimeField that does what you want, using pre_save. Try:
class Table1(models.Model):
f = models.DateTimeField(auto_now=True)
(If you must write your own pre_save, look at how auto_now modifies the actual model instance in /django/db/models/fields/__init__.py on lines 486-492:
def pre_save(self, model_instance, add):
if self.auto_now or (self.auto_now_add and add):
value = datetime.datetime.now()
setattr(model_instance, self.attname, value)
return value
else:
return super(DateField, self).pre_save(model_instance, add)
)

Categories