How to extend Django generic year archive view? - django

I'm using Django's generic year archive view to display event objects by year. This may or may not be the best way to do this since I found that Django restricts the object list to the year being passed; my date range spans the current year into the next.
Here's my view:
class VisitingScholarsYearView(YearArchiveView):
allow_empty = True
allow_future = True
date_field = 'event_date'
template_name = "events/dvs_test.html"
context_object_name = 'event_list'
make_object_list = True
def get_queryset(self):
return Event.school_year_events.from_year(self.get_year()).filter(event_type__title='Distinguished Visiting Scholars Series')
Here's the manager on my model (an Event object with a DateField called event_date):
class SchoolYearManager(models.Manager):
def live_events(self, start_date, end_date):
return self.filter(status=self.model.LIVE).filter(event_date__range=(start_date, end_date))
def this_year(self):
now = datetime.datetime.now()
current_year = now.year
start_date = datetime.date(current_year, 7, 1)
end_date = datetime.date((current_year + 1), 6, 30)
return self.live_events(start_date, end_date)
def from_year(self, year):
start_date = datetime.date(int(year), 7, 1)
end_date = datetime.date((int(year) + 1), 6, 30)
return self.live_events(start_date, end_date)
And finally, my url for the view:
url(r'^distinguished-visiting-scholars-series/(?P<year>\d{4})/$', VisitingScholarsYearView.as_view()),
When I hit the API, I get the events I expect. But the YearArchiveView appears to limit the returned events to the year I give it; this is also expected, but I'd like it to span the range I refer to in the manager (ie July 1 to June 30 ).
How can I change this behavior? Or should I attempt a different view (ListView)?

I don't think you should use YearArchiveView as the base here - there's too much built-in logic around getting the objects for that date.
Instead, use ListView with YearMixin:
class VisitingScholarsYearView(YearMixin, ListView):

Related

Convert raw sql query to django orm

I written this query in PostgreSQL and I'm confused of conversion of this query to django orm
SELECT count(*),
concat(date_part('month', issue_date), '/', date_part('year', issue_date) ) as date
FROM affiliates_issuelog
WHERE tenant_id = '{tenant_id}'
GROUP BY date_part('month', issue_date),
date_part('year', issue_date)
ORDER BY date_part('year', issue_date) desc,
date_part('month', issue_date) desc
I have this model that records the insertion of new affiliates by date and by institution (tenant), only I need to receive from the query the total amount of records inserted per month in the year, and I was using the listview to make my pages until then but I don't know how to filter this data using orm.
class IssueLog():
tenant = models.ForeignKey("tenants.Federation", on_delete=models.CASCADE)
issue_date = models.DateField(
default=date.today, verbose_name=_("date of issue")
)
class Meta:
verbose_name = _("Entrada de emissão")
verbose_name_plural = _("Entradas de emissão")
def __str__(self):
return f"{self.institution}, {self.tenant}"
My pages that return a list of data I did as the example below, is it possible to pass the data as I want through get_queryset()?, I already managed to solve my problem using the raw query, but the project is being done only with orm so I wanted to keep that pattern for the sake of the team. Ex:
class AffiliateExpiredListView(HasRoleMixin, AffiliateFilterMxin, ListView):
allowed_roles = "federation"
model = Affiliate
ordering = "-created_at"
template_name_suffix = "issued_list_expired"
paginate_by = 20
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["renew_form"] = AffiliateRenewForm()
tenant_t = self.request.user.tenant
context["cancel_presets"] = tenant_t.cancelationreason_set.all()
return context
def get_queryset(self):
return super().get_queryset().filter(is_expired=True).order_by('full_name')
You can query with:
from django.db.models import Count
from django.db.models.functions import ExtractMonth, ExtractYear
IssueLog.objects.values(
year=ExtractYear('issue_date'),
month=ExtractMonth('issue_date')
).annotate(
total=Count('pk')
).order_by('-year', '-month')
This will make a queryset with dictionaries that look like:
<QuerySet [
{'year': 2022, 'month': 2, 'total': 14},
{'year': 2022, 'month': 1, 'total': 25},
{'year': 2021, 'month': 12, 'total': 13}
]>
I would not do string formatting in the database query, but just do this in the template, etc.
But the model can not be abstract = True [Django-doc]: that means that there is no table, and that it is only used for inheritance purposes to implement logic and reuse it somewhere else.

Django DateField with only month and year

I post this because I could not find an answer/example that satisfied me and I solved it a way I haven't read in there. Maybe it can be useful or can be discussed.
Basically, my app displays formsets where users can create objects. Once validated, the formset is displayed again and user can add new objects.
Specifying a day is not relevant.
I first started to use some javascript to do it, then played with Django custom model (to subclass models.DateField).
I could not succeed to do it with the later (my post updated linked to this one)
As I have deadlines, I did it with simple ChoiceField storing objects as Integers and then, use a #property method to build a proper date out of those user inputs (add 1 as day).
This way I avoided to deep dive in Django steam pipes and even got rid of a datepicker!
But thanks to the #property, I can still keep the convenience of formatting dates in Django templates and make date calculation etc...
models.py :
NOTE : make use to return None if the date cannot be built out of filled fields, otherwise you'll have an unbound error when trying to render those #property field in a template.
class MyModel(models.Model):
YEAR_CHOICES = [(y,y) for y in range(1968, datetime.date.today().year+1)]
MONTH_CHOICE = [(m,m) for m in range(1,13)]
start_year = models.IntegerField(choices=YEAR_CHOICES,
default=datetime.datetime.now().year,)
start_month = models.IntegerField(choices=MONTH_CHOICE,
default=datetime.datetime.now().month,)
end_year = models.IntegerField(choices=YEAR_CHOICES,
default=datetime.datetime.now().year,)
end_month = models.IntegerField(choices=MONTH_CHOICE,
default=datetime.datetime.now().month,)
#property
def start_date(self):
if self.start_year and self.start_month:
return datetime.date(self.start_year, self.start_month, 1)
elif self.start_year:
return datetime.date(self.start_year, 12, 31)
else:
return None
#property
def end_date(self):
if self.end_year and self.end_month:
return datetime.date(self.end_year, self.end_month, 1)
elif self.end_year:
return datetime.date(self.end_year, 12, 31)
else:
return None
forms.py
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
exclude = ("blabla",)
Now, if you want to display a date in a form template, you can use :
{{ form.instance.start_date }}
Hope it helps & Suggestions are welcome!
it was not too clear for me, what are you doing exactly?
if you want to show a date string which has only years and month. that would be achieved by using template-filter date and a format e.g: {{ obj.created_at|date:'%y %m' }}.
but if I'm wrong and may you want to have a custom filed in your modelForm to pick a date(which only has year and month) then something like you've already did is the idea, but the code is the matter
I think your start_date and end_date function need to be changed:
def start_date(self):
if self.start_year and self.start_month:
return datetime.date(self.start_year, self.start_month, 1)
elif self.start_year:
# return Jan 1st
return datetime.date(self.start_year, 1, 1)
else:
return None
def end_date(self):
if self.end_year and self.end_month:
if self.end_month == 12:
# if month is December, return December 31st
return datetime.date(self.end_year, self.end_month, 31)
else:
# else, get first day of next month and subtract 1 day.
return datetime.date(self.end_year, self.end_month+1, 1) - datetime.timedelta(days=1)
elif self.end_year:
return datetime.date(self.end_year, 12, 31)
else:
return None

Django ORM: Conditional filtering

In a Django app, I keep daily scores of users in such a model:
class Score(models.Model):
user = models.ForeignKey(User)
score = models.PositiveIntegerField(default=0)
date = models.DateField(auto_now_add=True)
I want to find out the days when a user's score has changed drastically compared to a consequent day. That is, if for example, the user scores 10 times higher than the previous day.
How can I include such a condition in a query filter using Django ORM? Is it possible with a single query using conditional expressions as described here: https://docs.djangoproject.com/en/1.9/ref/models/conditional-expressions/
Thanks.
If you change your Score class slightly to include the previous day's score (which is admittedly pretty wasteful), you can pack the query into one line using F expressions.
Your new class:
class Score(models.Model):
user = models.ForeignKey(User)
score = models.PositiveIntegerField(default=0)
lastscore = models.PositiveIntegerField(default=0)
date = models.DateField(auto_now_add=True)
Then the filter becomes:
from django.db.models import F
daily_chg = 10
big_changes = Score.objects.filter(score__gt=daily_chg*F('lastscore'))
Instead of using timedeltas to search for and set the previous day's score field, I'd look into establishing an ordering via a Meta class and calling latest() when saving the current day's score.
Using timedelta we can test for the last week's days for a given user as such:
from my_app.models import Score
import datetime
def some_view(request):
days_with_score_boost = []
today = datetime.date.today()
for each_day in xrange(0,7):
day_to_test, day_before_to_test = today - datetime.timedelta(days=each_day), today - datetime.timedelta(days=each_day + 1)
day_before_score = Score.objects.get(user=request.user,date=today - datetime.timedelta(days=each_day)).score # will need to catch the exception raised if .get return None and process appropriately
if Score.objects.filter(user=request.user,score__gte=days_before_score * 10,date=day_before_to_test).count() > 0:
days_with_score_boost.append(day_to_test)
days_with_score_boost will be a list of datetime.date objects where the score increased by 10 or more from the day before.
In reponse to your comment, I'd make a measure that checks at save time whether the score boost has occured. However, I would get rid of auto_now_add in favor of writing in the save method.
from django.utils import timezone
from django.core.exceptions import ObjectDoesNotExist
class Score(models.Model):
user = models.ForeignKey(User)
score = models.PositiveIntegerField(default=0)
date = models.DateField(null=True,blank=True)
increased_ten_over_previous_day = models.BooleanField(null=True,blank=True)
def save(self, *args, **kwargs):
self.date = timezone.now().today()
try:
yesterday_score = Score.objects.get(date=self.date-timezone.timedelta(days=1)).score
self.increased_ten_over_previous_day = (yesterday_score * 10) <= self.score
except ObjectDoesNotExist: # called if Score.object.get returns no object; requires you only have one score per user per date
self.increased_ten_over_previous_day = False
super(self, Score).save(*args, **kwargs)
Then you could filter objects for a date_range where increased_ten_over_previous_day is True.

filtering recoreds through date range in django view

I'm trying to filter data through view. I tried the __range and __gte + __lte but there are not results on the website.
model:
class Change(models.Model):
def __unicode__(self):
return unicode(self.number)
number = models.IntegerField(verbose_name="CHG")
service = models.ForeignKey('organization.Service')
environment = models.CharField(choices=ENV_CHOICE, max_length=20)
description = models.CharField(max_length=50)
start_date = models.DateField()
start_time = models.TimeField()
end_date = models.DateField()
end_time = models.TimeField()
assignee = models.ForeignKey('organization.Assignee')
I tried different approach with the view:
1
def home(request):
changes = Change.objects.all().filter(start_date__gte=datetime.date.today(), end_date__lte=datetime.date.today())
return render(request, 'index.html', {'changes' : changes})
In this case when only start_date__gte is used it's working correctly but when I add the end_date__lte it does not display any records.
2
def home(request):
today = datetime.date.today()
changes = Change.objects.all(today__range=[start_date, end_date])
return render(request, 'index.html', {'changes' : changes})
In this case I get and error global name start_date is not defined.
Thank you very much for your help.
The problem is that you don't understand how range lookup works.
It is used like this:
Change.objects.filter(change_field__range=[start_datatime, end_datetime])
where change_field is a datetime or date field of Change model on which you want to query. start_datetime and end_datetime are datetime objects which you provide. For example:
class Change(models.Mode):
...
published = models.DateTimeField()
...
so your query may look like
Change.objects.filter(published__range=[datetime.datetime(2011, 2, 4), datetime.datetime(2011, 5,4)])
This will return queryset with Change objects which were published between 2011-2-4 and 2011-5-4
It seems to me that in your above code, timedelta(Difference between start and end date) between start and end date is 0. If your start and end date is same, you should add some timedelta in your end time like this.
def home(request):
changes = Change.objects.all().filter(start_date__gte=datetime.date.today(), \
end_date__lte=datetime.date.today() + datetime.timedelta(seconds=86400))
return render(request, 'index.html', {'changes' : changes})

Business days in Django

I need to implement a conditional break which uses business days. I have a class with a DateField, and if that date is less than 5 business days in the future, something (action a) happens, else b happens. How can I determine the number of business days between two objects?
Obviously I'll need to calculate what 5 business days from today is. Finding 5 days in the future would be easy, using a simple time-delta, but to account for business days, it gets more complicated. I think I can safely ignore holidays for now (it's not the best case, but I think I can make due with just having Business days be Monday-Friday). Can anyone give me some guidance as to how I could do something like: target = today + 5 business_days?
Thanks
Here's a generic solution, even though your case is embarrassingly simple ;P
from datetime import timedelta, date
def add_business_days(from_date, number_of_days):
to_date = from_date
while number_of_days:
to_date += timedelta(1)
if to_date.weekday() < 5: # i.e. is not saturday or sunday
number_of_days -= 1
return to_date
And the result.
>>> date.today()
datetime.date(2013, 7, 25)
>>> add_business_days(date.today(), 6)
datetime.date(2013, 8, 2)
Bonus marks if you check whether a date falls on a holiday in the if statement.
This Example is when you are making a django project as an API
You can get business days with Numpy which uses SCIPY(check for more details)
You will have to install numpy through pip
Python 2.7
pip install numpy
Python 3
pip3 install numpy
In models.py add depending on where you want to put your property method :
class WorkingDay(models.Model):
# YY-MM-DD
start = models.DateField(null=True, verbose_name="start")
end = models.DateField(null=True, verbose_name="end")
#property
def workdays(self):
total = np.busday_count(self.start, self.end)
return total
Then in your serializers.py you might have something like this.
class WorkingDaySerializer(serializers.ModelSerializer):
workdays = serializers.IntegerField(read_only=True)
class Meta:
model = WorkingDay
fields = '__all__'
read_only_fields = ['workdays']
In your views you might do something like this.
class WorkingDayAPI(APIView):
"""
{ "start":"2018-01-01",
"end":"2018-05-01"
}
GET work days between dates
"""
serializer_class = WorkingDaySerializer
def get(self, request, format=None):
business_days = WorkingDay.objects.all()
serializer = WorkingDaySerializer(business_days, many=True)
return Response(serializer.data)
def post(self, request, *args, **kwargs):
start = request.data.get('start')
end = request.data.get('end')
# bizdays = request.data.get('bizdays')
business_days = WorkingDay.objects.create(**request.data)
business_days.start = start
business_days.end = end
business_days.workdays = workdays
business_days.save()
serializer = WorkingDaySerializer(business_days)
return Response(serializer.data, status=status.HTTP_201_CREATED)