Why does datetime__range work incorrectly? - django

views
import datetime
from .models import AccountTransaction
date = datetime.datetime.today()
def account_transactions_week_view(request):
account_transactions = AccountTransaction.objects.filter(user_id = request.user.id).filter(datetime__range=[date - datetime.timedelta(days=7), date])
models
class AccountTransaction(models.Model):
user = models.ForeignKey(User, verbose_name=_('user'))
datetime = models.DateTimeField(_('created at'), auto_now_add=True)
I bring to the page a list of recent entries over the past week. For this I use a filter.
I wondered why this variant does not work correctly (new entries appear after a while):
filter(datetime__range=[date - datetime.timedelta(days=7), date])
But this variant works correctly:
filter(datetime__gt=date - datetime.timedelta(days=7))
I wonder what's wrong with the first one?
There are middleware
class TimezoneMiddleware(object):
def process_request(self, request):
tzname = request.session.get('django_timezone')
if not tzname:
request.session['django_timezone'] = 'Europe/Kiev'
tzname = 'Europe/Kiev'
timezone.activate(pytz.timezone(tzname))
settings
TIME_ZONE = 'UTC'

You've defined date outside the method. That means the definition is executed when the module is first imported, when the Django process starts up. It will keep the same value for all uses of that process, until the server decides to recycle it and create a new one. So your range query will use the original value as the end point for all queries during the lifetime of the process.
The solution is to simply move the definition to inside the view function.
(Your other query works because it is simply doing "everything greater than 7 days since the original date", which automatically includes things greater than the original date.)

Related

How do I limit requests per user in Django rest framework using throttling?

I'm working on a Quiz app and one question gets uploaded every week in a month (4 ques/month). Now I want to make sure that a logged in user can only attempt the question twice per week and not more than that. How can I do this using throttling or any other way?
Here's my Quiz model:
Days = (
("Mon", "Monday"),
("Tue", "Tuesday"),
("Wed", "Wednesday"),
("Thu", "Thursday"),
("Fri", "Friday"),
("Sat", "Saturday"),
("Sun", "Sunday")
)
class QuizDetail(models.Model):
name = models.CharField(max_lenght=255, blank=False, null=False)
start_date = models.DateTimeField()
end_date = models.DateTimeField()
publisehd_week_day = models.CharField(max_length=255, choices=Days)
The published_week_day can change every month, so basically one month it can be Tuesday and next month it can be Thursday.
Note: If in a month published_week_day is Tuesday and a user attempts last week's quiz on Monday and exhausts his two attempts then on Tuesday he should be able to attempt as it will be a fresh quiz.
Store it in another field
I am assuming end_date only stores the last time the quiz ended.
Create a field, say count. Now increase count by 1 everytime a test is attempted. If it reaches above 2, dont allow the quiz to be taken.
What about resetting it?
You could calculate it every single time you make the request. However this is clearly extremely ineffiecient. Therefore, I would do something like so:
Create another process.(Have a look at Simple approach to launching background task in Django)
That process constantly searches through the database at fixed intervals.
If it finds that a week has passed since the last time the user took the test, it resets the count to 0. To do this simple subtract current datetime(datetime.now()) with end_time and compare it to 2 weeks.
You have to create one model which basically would have a relationship with your QuizDetail model
class UserQuizAttempt(models.Model)
quiz = models.ForeignKey(QuizDetail, on_delete=models.CASCADE)
user = models.ForeginKey(User, on_delete=models.CASCADE)
attempt_made_on = models.DateTimeField(auto_now_add=True)
so in your views.py file, where the user will make an API call to attempt the quiz you have to check whether the user has made any attempt for that particular quiz in that particular week. If it is exceeding 2 then just return a response saying that he's exceeding the limit of the attempt for the week.
a basic example could look like this
from datetime import date
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated
class QuizAPI(APIView):
authentication_classes = [SessionAuthentication]
permission_classes = [IsAuthenticated]
def check_user_attempts(self, request, quiz_instance):
current_week = date.today().isocalendar()[1]
attempts = UserQuizAttempt.objects.filter(attempt_made_on__week=current_week=current_week, quiz=quiz_instance, user=request.user)
if attempts.count() > 2:
return False
return True
def post(self, request):
quiz_id = request.data.get('quiz_id')
quiz_instance = QuizDetail.objects.get(id=quiz_id)
if self.check_user_attempts(request, quiz_instance):
UserQuizAttempt.objects.create(quiz=quiz_instance, user=request.user)
# your logic goes here
...
else:
return Response("Your attempts exceeding for the week! Please try next week", status=status.HTTP_406_NOT_ACCEPTABLE)
So with this, you will have the history of the user's attempt made on the quiz which can be used for reporting or something.

Displaying / Testing outputs are correct. Sanity Check

I am still learning Django and slowly improving but I have a few questions, I have my whole model below:
from django.db import models
from datetime import datetime, timedelta
# Create your models here.
year_choice = [
('year1','1-Year'),
('year3','3-Year')
]
weeksinyear = 52
hours = 6.5
current_year = datetime.year
class AdminData(models.Model):
year1 = models.IntegerField()
year3 = models.IntegerField()
#property
def day_rate_year1(self):
return self.year1 / weeksinyear / hours
class Price(models.Model):
name = models.CharField(max_length=100)
contract = models.CharField(max_length=5, choices=year_choice)
start_date = models.DateField(default=datetime.now)
end_date = models.DateField(default=datetime(2021,3,31))
def __str__(self):
return self.name
The main concern for me at the moment is trying to understand if my function def day_rate_year1(self): is working correctly, if someone could point me in the right direction to understand either how I display this as a string value in a template or test in the shell to see if the value pulls through as the values for year1 and year3 can change based on user input.
I am trying to work out the day rate so I can then use the start and end dates and work out the number of days between the two to calculate a price which is then displayed to the user which can again change depending on the number of days and the contract type which is a 3 year option or 1 year option.
Let me know if you need the views or templates as well.
Thanks for the help!
if someone could point me in the right direction to understand either how I display this as a string value in a template or test in the shell to see if the value pulls through as the values for year1 and year3 can change based on user input
if you launch a shell session as below, you should see the output of your property.
python manage.py shell
Expected output:
>>> from app_name.models import AdminData
>>> test = AdminData.objects.create(year1=2010, year3=2016)
>>> print(test.day_rate_year1)
5.946745562
>>>

Django/Python How do I get my receiver to add a value to a field relative to a previous addition - instead of indiscriminately adding to the value?

Python3.6.4; Django 2.0. Long time first time, please be gentle
I'm creating a tracking application that accepts multiple work logs to a work order. I'm trying to track time spent on a workorder by obtaining a time delta from each log and sending it to the work order model to hold the aggregate time.
The problem is, each time I'm updating an existing log it adds the entire time to the work order instead of just the difference. So if a log was previously 12-2:00 (feeds 2 hours to WorkOrder), and you changed it to 12-1:30 it was feed an additional 1.5 hours to WorkOrder, instead of subtracting 30min
Is there a way I can check to see a time was previously sent to WorkOrder? I tried updating update_labor_hours to check if the timedelta was < or > the original time, but I couldn't really figure it out.
Any help is appreciated!
from django.utils.timezone import now
from datetime import datetime, date, timedelta
from django.db import models
from django.db.models.signals import post_save
# Create your models here.
class WorkOrder(models.Model):
labor_hours = models.DurationField(blank=True, null=True)
class WorkLog(models.Model):
work_order = models.ForeignKey(WorkOrder, on_delete=models.PROTECT)
start_time = models.TimeField(blank=True, null=True)
end_time = models.TimeField(blank=True, null=True)
def _time_spent(self):
return datetime.combine(date.today(), self.end_time) - datetime.combine(date.today(), self.start_time)
time_spent = property(_time_spent)
def update_labor_hours(sender, instance, *args, **kwargs):
instance.work_order.labor_hours = instance.work_order.labor_hours + instance.time_spent
instance.work_order.save()
post_save.connect(update_labor_hours, sender=WorkLog)

Make a change to date fields automatically in Django

I have 2 date fields and I want to update the date automatically.
The code won't work until I update the updated field by myself.
How can I make it update itself (updated fields) automatically?
STATUS_CHOICES = (
('P', 'Paid'),
('U', 'UNPAID'),
)
status = models.CharField(
max_length=1, choices=STATUS_CHOICES)
updated = models.DateTimeField(default=datetime.now() )
expiry = models.DateTimeField(default=datetime.now() + timedelta(days=30) )
def save(self):
if(self.expiry >= self.updated):
self.status = default = "P"
else:
self.status = default = "U"
self.expiry = default=self.updated+timedelta(days=1)
super(Users, self).save()
The DateTimeField have a auto_now property, that will set the field to the current date every time the object is saved.
The main question is what event should trigger the save action. For example you can improve your view, so that every time someone visit the page, your model would update as well as desired DateTimeField. On the other hand you may want this happen on the schedule basis, so you should use something like Cron, Celery or Huey.
In your case you have two options.
Make some code that will be executed periodically that will update the status based on current date.
Do not store status in the database at all. If it can depend only from expiry field it can be a computed property
P.S.
It's not related to what you are asking but I have a suggestion for you. Never ever use the direct result from datetime.now() as default value for DateTimeField. It's misleading for you and it's not the same as what you want to achieve. datetime.now will be executed at import time (when the server starts) and will stay the same until next restart.
If you want the default value to be the exact time when the record was created then default value need to be a function (which will be executed every time) and not the result of the function.
This means that updated need to be DateTimeFiled(default=datetime.now) (without the brackets).
This logic is more like for a field called created_at or date_created and not updated but this is your logic.

How to set a Django model field's default value to a function call / callable (e.g., a date relative to the time of model object creation)

EDITED:
How can I set a Django field's default to a function that gets evaluated each time a new model object gets created?
I want to do something like the following, except that in this code, the code gets evaluated once and sets the default to the same date for each model object created, rather than evaluating the code each time a model object gets created:
from datetime import datetime, timedelta
class MyModel(models.Model):
# default to 1 day from now
my_date = models.DateTimeField(default=datetime.now() + timedelta(days=1))
ORIGINAL:
I want to create a default value for a function parameter such that it is dynamic and gets called and set each time the function is called. How can I do that? e.g.,
from datetime import datetime
def mydate(date=datetime.now()):
print date
mydate()
mydate() # prints the same thing as the previous call; but I want it to be a newer value
Specifically, I want to do it in Django, e.g.,
from datetime import datetime, timedelta
class MyModel(models.Model):
# default to 1 day from now
my_date = models.DateTimeField(default=datetime.now() + timedelta(days=1))
The question is misguided. When creating a model field in Django, you are not defining a function, so function default values are irrelevant:
from datetime import datetime, timedelta
class MyModel(models.Model):
# default to 1 day from now
my_date = models.DateTimeField(default=datetime.now() + timedelta(days=1))
This last line is not defining a function; it is invoking a function to create a field in the class.
In this case datetime.now() + timedelta(days=1) will be evaluated once, and stored as the default value.
PRE Django 1.7
Django [lets you pass a callable as the default][1], and it will invoke it each time, just as you want:
from datetime import datetime, timedelta
class MyModel(models.Model):
# default to 1 day from now
my_date = models.DateTimeField(default=lambda: datetime.now() + timedelta(days=1))
Django 1.7+
Please note that since Django 1.7, usage of lambda as default value is not recommended (c.f. #stvnw comment). The proper way to do this is to declare a function before the field and use it as a callable in default_value named arg:
from datetime import datetime, timedelta
# default to 1 day from now
def get_default_my_date():
return datetime.now() + timedelta(days=1)
class MyModel(models.Model):
my_date = models.DateTimeField(default=get_default_my_date)
More information in the #simanas answer below
[1]: https://docs.djangoproject.com/en/dev/ref/models/fields/#default
Doing this default=datetime.now()+timedelta(days=1) is absolutely wrong!
It gets evaluated when you start your instance of django. If you are under apache it will probably work, because on some configurations apache revokes your django application on every request, but still you can find you self some day looking through out your code and trying to figure out why this get calculated not as you expect.
The right way of doing this is to pass a callable object to default argument. It can be a datetime.today function or your custom function. Then it gets evaluated every time you request a new default value.
def get_deadline():
return datetime.today() + timedelta(days=20)
class Bill(models.Model):
name = models.CharField(max_length=50)
customer = models.ForeignKey(User, related_name='bills')
date = models.DateField(default=datetime.today)
deadline = models.DateField(default=get_deadline)
There's an important distinction between the following two DateTimeField constructors:
my_date = models.DateTimeField(auto_now=True)
my_date = models.DateTimeField(auto_now_add=True)
If you use auto_now_add=True in the constructor, the datetime referenced by my_date is "immutable" (only set once when the row is inserted to the table).
With auto_now=True, however, the datetime value will be updated every time the object is saved.
This was definitely a gotcha for me at one point. For reference, the docs are here:
https://docs.djangoproject.com/en/dev/ref/models/fields/#datetimefield
Sometimes you may need to access model data after creating a new user model.
Here is how I generate a token for each new user profile using the first 4 characters of their username:
from django.dispatch import receiver
class Profile(models.Model):
auth_token = models.CharField(max_length=13, default=None, null=True, blank=True)
#receiver(post_save, sender=User) # this is called after a User model is saved.
def create_user_profile(sender, instance, created, **kwargs):
if created: # only run the following if the profile is new
new_profile = Profile.objects.create(user=instance)
new_profile.create_auth_token()
new_profile.save()
def create_auth_token(self):
import random, string
auth = self.user.username[:4] # get first 4 characters in user name
self.auth_token = auth + ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits + string.ascii_lowercase) for _ in range(random.randint(3, 5)))
You can't do that directly; the default value is evaluated when the function definition is evaluated. But there are two ways around it.
First, you can create (and then call) a new function each time.
Or, more simply, just use a special value to mark the default. For example:
from datetime import datetime
def mydate(date=None):
if date is None:
date = datetime.now()
print date
If None is a perfectly reasonable parameter value, and there's no other reasonable value you could use in its place, you can just create a new value that's definitely outside the domain of your function:
from datetime import datetime
class _MyDateDummyDefault(object):
pass
def mydate(date=_MyDateDummyDefault):
if date is _MyDateDummyDefault:
date = datetime.now()
print date
del _MyDateDummyDefault
In some rare cases, you're writing meta-code that really does need to be able to take absolutely anything, even, say, mydate.func_defaults[0]. In that case, you have to do something like this:
def mydate(*args, **kw):
if 'date' in kw:
date = kw['date']
elif len(args):
date = args[0]
else:
date = datetime.now()
print date
Pass the function in as a parameter instead of passing in the result of the function call.
That is, instead of this:
def myfunc(date=datetime.now()):
print date
Try this:
def myfunc(date=datetime.now):
print date()