How do I paginate WeekArchiveView? - python

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.

Related

How to create and access dynamic variable names a template function?

I wound up having three extremely similar functions in my code, which were to take separate user inputs for their birth year, month, and day. I want to use the best practices I can even though the code is extremely basic, which is why it's a bit overkill. So right now I have a function that acts as a template and takes three args dateType, typeStr, and typeFormat. I'm trying to use these args to name variables in the three functions that I'll create using the template so that I can later print out the birth date of the person. I'm aware of scope, but I've tried making things global etc etc and it hasn't worked. I don't know if I'm even creating variables that are persistent or if it all gets unassigned once the function is done, and if I am creating variables I can't figure out what they're called as I've tried every possibility.
def makeTemplateInput(dateType, typeStr, typeFormat):
def templateInput(dateType, typeStr, typeFormat):
print('You\'re using the template function. Please input your birth {} in the format {}.\n$ '.format(dateType, typeFormat), end='')
while True:
try:
dateType = input()
typeStr = dateType
dateType = int(dateType)
if dateType > 0:
if len(typeStr) != len(typeFormat) and isinstance(dateType, int):
print('Your input doesn\'t have {} digits, please use the format {}.\n$ '.format(str(len(typeFormat)), typeFormat), end='')
print('strlen: ' + str(len(typeStr)))
continue
break
else:
print('Your input is not a positive integer!\n$ ', end='')
continue
except ValueError:
print('Your input is not a valid number, please use the format {} where {} is an integer.\n$ '.format(typeFormat, str(typeFormat[:1])), end='')
print(dateType)
def yearInput1():
makeTemplateInput('year', 'yearStr', 'YYYY')
yearInput1()
print('Your birthdate is: {}/{}/{}'.format(yearInput1.year, monthInput.monthStr, dayInput.dayStr))
I'm sure it's a mess that misunderstands fundamental things about Python, but I really don't understand what's gone wrong.
A few online classes or some good books can help with some of this confusion. I've created some sample code to hopefully answer your question. Thank you.
class Calendar:
def __init__(self, day, month, year): #This can be initialised with the day/month/year
self.day = day
self.month = month
self.year = year
def displayDate(self): #This will display the dates from the object created/set
print('day:{} month:{} year:{}'.format(self.day, self.month, self.year))
def setNewDate(self, otherDay, otherMonth, otherYear): #this sets the class attributes to something else
print('This was the old values day:{} month:{} year:{}'.format(self.day, self.month, self.year))
self.day = otherDay
self.month = otherMonth
self.year = otherYear
print('The new values are day:{} month:{} year:{}'.format(self.day, self.month, self.year))
cal = Calendar(6,11,2020)
cal.displayDate()
cal.setNewDate(3,4,2099)

Override save in Django causing infinite recursion error

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

Django - Use getattr() to retrieve Model Method (#property)

I would like to do this:
def retrieve_data(self, variable):
start_date = datetime.date.today() - datetime.timedelta(int(self.kwargs['days']))
invoice_set = InvoiceRecord.objects.filter(sale_date__gte=start_date)
total = 0
for invoice in invoice_set:
for sale in invoice.salesrecord_set.all():
total += getattr(self, variable)
return round(total)
Where variable is submitted as a string that represents one of my model methods:
#property
def total_sale(self):
return self.sales_price * self.sales_qty
But my effort doesn't work:
def total_sales(self):
return self.retrieve_data(variable="total_sale")
It simply says:
'SalesSummaryView' object has no attribute 'total_sale'
Evidently, I am misunderstanding the usage. Can someone help me figure out a way to accomplish this goal?
Got it! I was calling getattr() on the view, rather than the model. Instead of using self, I needed to submit the sale object.
for invoice in invoice_set:
for sale in invoice.salesrecord_set.all():
total += getattr(sale, variable)

Wagtail: Get previous or next sibling

I'm creating a page with wagtail where I need to know the previous and next sibling of the current page:
In my portrait page model, I tried to define two methods to find the correct urls, but I'm missing a crucial part. To get the first sibling, I can just do the following:
class PortraitPage(Page):
...
def first_portrait(self):
return self.get_siblings().live().first().url
There is the first() and last() method, but there doesn't seem to be a next() or previous() method to get the direct neighbours (in the order that they are arranged in the wagtail admin).
Is there any way to achieve this?
Django-Treebeard provides get_next_sibling and get_prev_sibling which will return your direct siblings in the tree, but these are not necessarily your next published sibling. To request those you can use:
prev = page.get_prev_siblings().live().first()
next = page.get_next_siblings().live().first()
Which can obviously also be chained with any other queryset operations.
After going through the debugger for a while, I found out that wagtail already has two methods: get_prev_sibling() and get_next_sibling().
So the methods could look like this (accounting for the first page in the previous method and the last item in the next method):
def prev_portrait(self):
if self.get_prev_sibling():
return self.get_prev_sibling().url
else:
return self.get_siblings().last().url
def next_portrait(self):
if self.get_next_sibling():
return self.get_next_sibling().url
else:
return self.get_siblings().first().url
Here's a version handling non-published siblings.
def next_portrait(self):
next_sibling = self.get_next_sibling()
if next_sibling and next_sibling.live:
return next_sibling.url
else:
next_published_siblings = self.get_next_siblings(
inclusive=False
).live()
if len(next_published_siblings):
return next_published_siblings[0].url
return self.get_siblings().live().first().url
def prev_portrait(self):
previous_sibling = self.get_prev_sibling()
if previous_sibling and previous_sibling.live:
return previous_sibling.url
else:
previous_published_siblings = self.get_prev_siblings(
inclusive=False
).live()
if len(previous_published_siblings):
return previous_published_siblings[0].url
return self.get_siblings().live().last().url
You can define properties in your class that inherits from Page
Siblings
If you want the siblings of an instance of Page, you can use the following (based on Danielle Madeley's answer):
class PortraitPage(Page):
# ...
#property
def next_sibling(self):
return self.get_next_siblings().live().first()
#property
def prev_sibling(self):
return self.get_prev_siblings().live().first()
Siblings of the same class
If you want the the siblings of PortraitPage, specify self.__class__ in the type method as follows:
class PortraitPage(Page):
# ...
#property
def next_sibling(self):
return self.get_next_siblings().type(self.__class__).live().first()
#property
def prev_sibling(self):
return self.get_prev_siblings().type(self.__class__).live().first()
Template
If you want to use them in a template, after defining the properties, do the following:
{# This is a template #}
Previous Sibling: {{ page.next_sibling }}
Next Sibling: {{ page.prev_sibling }}
self.get_siblings() falls over if you want to do any filtering based on properties only found in the subclass since the PageQuerySet results are of type Page.
For me, I have a blog index page that can be filtered by category or tag.
The bog detail page has cards for the next and previous blog post at the end.
I wanted those prev/next posts to be according to the filter I landed on that page from.
To get around this, you need to query the objects belonging to the subclass (eg BlogDetailPage), filter those, then get the prev/next post using class.objects.sibling_of(self):
def get_context(self, request, *args, **kwargs):
context = super().get_context(request, *args, **kwargs)
siblings = self.__class__.objects.sibling_of(self).live()
category_filter = request.GET.get("category", None)
tag_filter = request.GET.get("tag", None)
if category_filter:
siblings = siblings.filter(categories__slug__in=category_filter.split(","))
context["filter"] = '?category=' + category_filter
elif tag_filter:
siblings = siblings.filter(tags__slug__in=tag_filter.split(','))
context["filter"] = '?tag=' + tag_filter
else:
context["filter"] = ''
context["next_post"] = siblings.filter(path__gt=self.path).first()
context["previous_post"] = siblings.filter(path__lt=self.path).first()
return context
I used self.__class__.objects.sibling_of(self) as this is in a super class with sub classed blog pages.

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