Allow data insertion only after time duration threshold in django - python

I have a model in Django with a DateTimeField attribute. I want to forbid insertion of new data in the database if the duration between the datetime field of the new data and the latest datetime field in the database is less than some duration threshold.
class MyModel(models.Model):
time_stamp = models.DateTimeField(default=timezone.now, null=True)
When I want to insert a datapoint say today, and the latest time stamp in my database is yesterday, and the duration threshold is one month (this operation should not be possible).

You can define this logic in your views like so:
from django.shortcuts import get_object_or_404
import datetime
def CreateNew(request, id):
obj = get_object_or_404(MyModel, id = id) #Get the object from your database
form = YourForm(request.POST or None, instance = obj) #create form instance to be rendered inside template
diff = (timezone.now() - obj.time_stamp).total_seconds()
threshold = datetime.timedelta(days=30).total_seconds()
if diff < threshold: # Compare dates to check condition
return HttpResponse('<h1>Not Allowed</h1>')
elif form.is_valid(): # If condition is passed save form as you normally would
form.instance.time_stamp = timezone.now() # Update time_stamp to current time
form.save()
return HttpResponseRedirect("/")
context = {
'form': form
}
return render(request, "Your_template", context)

If you are determined that this is prevented in a more hard manner than putting protection logic in view(s), then instead of checking in the view you can check in the model's save method.
def save( self, *args, **kwargs):
diff = (timezone.now() - self.time_stamp).total_seconds()
threshold = datetime.timedelta(days=30).total_seconds()
if diff < threshold: # Compare dates to check condition
# not certain ValueError is the best choice of exception
raise ValueError(
f"Can't save because {diff} seconds since the previous save, the minimum is {theshold}"
)
super().save( *args, **kwargs)
This check can still be bypassed, by Django bulk_update for example, and by raw SQL. Some databases may let you put the check into the database itself.
The downside is that fixing mistakes using (for example) the Django Admin may become difficult. In this case you can programmatically bypass the check by resetting the timestamp first.

Related

How can I Generate 10 Unique digits in Model Form and Pass Form Context Variable in Django Class Based ListView

I am new to Django Class Based Views and I am working on a project where on the template I want to have Form for creating customer accounts on the left and list of existing customers on the right.
So far I have the list of existing customers displayed but for the form I don't know how to pass its variable context to the same template, or it is not possible to Pass a Form that would be submitted inside a ListView Method. And I also want to generate unique account numbers of 10 Digits in ModelForm which I want the form field to be auto-filled and disabled
Here is my form code:
import secrets
#I want to Generate Account Number of 10 Digits but getting only 2
account = secrets.randbits(7)
#class for Customer Account Form
class CustomerAccountForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().init(*args, **kwargs)
self.fields['accountnumber'].initial = account
class Meta:
model = Customer
fields = ['accountnumber','surname','othernames','address','phone']
Code for my views (ListView)
class CustomerListView(ListView):
model = Customer
form_class = CustomerAccountForm
template_name = 'dashboard/customers.html'
#Function to get context data from queries
def get_context_data(self, **kwargs):
context_data = super().get_context_data(**kwargs)
#Get Day of today from current date and time
now = datetime.datetime.now()
#Get the date today
date_today = datetime.datetime.now().date
#Count Number of Withdrawals Today and passing in context
context_data['count_withdrawals_today'] = Witdrawal.objects.filter(date__year=now.year, date__month=now.month, date__day=now.day).count()
context_data['count_deposits_today'] = Deposit.objects.filter(date__year=now.year, date__month=now.month, date__day=now.day).count()
context_data['count_accounts'] = Customer.objects.count()
context_data['count_users'] = User.objects.count()
#Calculate today Deposit Today
context_data['total_deposit']= Deposit.objects.filter(date__year=now.year, date__month=now.month, date__day=now.day).aggregate(total_deposit=Sum('deposit_amount')).get('total_deposit') or 0
#Calculate today Withdrawal Today
context_data['total_withdrawal']= Witdrawal.objects.filter(date__year=now.year, date__month=now.month, date__day=now.day).aggregate(total_withdrawal=Sum('withdrawal_amount')).get('total_withdrawal') or 0
return context_data
Someone should please help me on how this is properly done and the form would be submitted successfully. Thanks in anticipation for your answer.
secrets.randbits(k) generates a random integer within a bit range.
For k=4 then Unsigned integer From 0 to 15.
For k=8 then Unsigned integer From 0 to 255.
For k=16 then Unsigned integer From 0 to 65,535, and so on.
If you want 10 random digits then you can use for example:
import random
account = "".join(str(random.randint(0, 9)) for _ in range(10))
After going through many tutorials and blogs on Django Class Based Views with ListViews for Forms, I discovered that ListViews was designed to populate Model items while FormViews is designed for creating and processing forms both can't be used on one template. Although many developers have had a way around it with the use of Multiple Mixins but no best practice has be mentioned for or against their findings yet.
In these case, I concluded that for a Django Template to be able to process Model Form and at the same time Populate Database items, it needs not to use Class Based Views but Function Based Views.

How to change dates when filtering a queryset in Django?

I want to display a list of all payments done by the company. But I want to be able to filter the dates, only showing payments for example:
In the last 30 days
In the last 90 days
Between two specific dates
Now, I know I can filter in my views between two dates, or before today for example.
But how I can I do this using something like a dropdown or a Data chooser on the template?
Something like this for reference.
For reference, the payments model would be this:
class Payment(models.Model):
name = models.CharField(max_length=250)
date = models.DateField(default=datetime.now)
value = models.DecimalField(max_digits=10, decimal_places=2, validators=[MinValueValidator(0.00)])
Use django forms with choice field and based on the value filter your queryset. e.g:
Choices:
PERIOD_LAST_THREE_MONTHS = "last-three-months"
PERIOD_LAST_SIX_MONTHS = "last-six-months" # ..
PERIOD_CHOICES = (
(PERIOD_LAST_THREE_MONTHS, _("Last 3 Months")),
(PERIOD_LAST_SIX_MONTHS, _("Last 6 Months")), ..
)
NOTE: variables separated with hyphens because they will be used in query string of http request.
Utils:
def get_period_date(period):
if period == PERIOD_LAST_THREE_MONTHS:
return timezone.now() - timezone.timedelta(days=90)
elif period == PERIOD_LAST_SIX_MONTHS:
return timezone.now() - timezone.timedelta(days=180)
This function will return a datetime object to filter your queryset with.
Forms:
class DateForm(forms.Form):
period = forms.ChoiceField(choices=PERIOD_CHOICES, required=False)
View:
class PaymentFormView(base.TempleView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['payments'] = Payment.objects.filter(date__gte=get_period_date(self.request.GET('period'))
return context
So basically based on the form value we get a date time object and filtered the queryset(Remember you have a date object). I have used template view instead of form view because I assume you do not want to accept post request. But again you can use form view and not allow post methods, this is up to you(It can be also done with function based views). Also consider the possibility that form can be empty so query string in http request may not have the period argument that may cause error and handle this situations how every you like.

Passing a date to a queryset based on a button click in Django

I have RoastManifest View that querys date_scheduled (field from my RoastManifest model) and returns distinct values. I pass that queryset to a template and loop over the values and present them as buttons on the HTML file. I am able to click any button and pass the corresponding date into the URL,((localhost/schedule/2019-06-20) when I click on the June, 20 2019 button) thus redirecting me to my RoastManifestDetailView. Now I want to be able to filter my RoastManifestDetailView based only on the date passed to the URL (or which date button was clicked).
I have tried RoastManifest.obejects.filter(date_scheduled__date=date.today())just to see if I could return anything schedule for today but I keep getting Fielderrors (Unsupported lookup 'date' for DateField or join on the field not permitted.). Please note I know that is not the exact queryset for me. I wish to pass in a variable into the queryset.
This is the model:
(NOTE: roast_order is in there only to allow for use of adminsortable2 library)
class RoastManifest(models.Model):
def batch_number_incrementer():
current_max = RoastManifest.objects.order_by('-batch_number').first()
if current_max:
return current_max.batch_number + 1
else:
return 8000000
batch_number = models.IntegerField(unique=True,
default=batch_number_incrementer,
)
product = models.ForeignKey(Product, related_name="products",
on_delete=models.PROTECT)
date_scheduled = models.DateField()
roaster_profile = models.ForeignKey(RoasterProfile,
on_delete=models.PROTECT)
roast_order = models.PositiveSmallIntegerField(default=0, blank=False, null=False)
class Meta:
ordering = ('roast_order',)
This is how I pull the individual days scheduled:
class RoastManifestListView(ListView):
model = RoastManifest
template_name = 'schedule/scheduled_days.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['days'] = RoastManifest.objects.order_by('date_scheduled').values('date_scheduled').distinct()
return context
This is the view I am having trouble with:
class RoastManifestDetailView(TemplateView):
template_name = 'schedule/roastmanifest_list.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["roasts"] = RoastManifest.objects.get(date_scheduled__date=date.today())
return context
I want to be able to click on a day (button) and pass that date into the query, thus returning everything scheduled for that specific day.
With the above RoastManifestDetailView I am currently getting a FieldError
Looking at the models above, I believe the correct query would be: RoastManifest.obejects.filter(date_scheduled=date.today())... your query was looking for a field called date on the scheduled_date field, but there is no such property.
When you are ready to query based on the clicked button, there are several ways to do it but the easiest (and "correct-ish-est" way) would be to pass the date in the URL as a query param: <button href="<URL to route>?date={item.scheduled_date}" ...> or something like that (you will probably have to play with date formats a bit to in your settings but you want something like 2019-06-21), and in your view you can get the value of that param using: date = request.GET.get('date', None) which will return the string value of the date query param which you can then use in queries and things (again possibly after some conversion) or None if there is no parameter with that name.

Django Form error: expected string or bytes-like object for datetime object?

I have a very simple django form for renting out keys that defaults a due date for the user, the user can change the date and then go on and save the form. However whenever I save the key_instance, django throws a error: expected string or bytes-like object. I know the datetime object is causing this error because if i comment it out everything works fine. Here is my views.py and forms.py
forms.py:
class UpdateKeyRequestForm(forms.Form):
APPROVE_CHOICES = [
('d', 'Deny this key request'),
('a', 'Approve this key request')
]
request_status = forms.CharField(label='Please select to accept or deny this request.',widget=forms.Select(choices=APPROVE_CHOICES))
due_date = forms.DateField(help_text='Enter a date (YYYY-MM-DD) between now and 4 weeks (default 3). ')
def clean_due_date(self):
due_date = self.cleaned_data['due_date']
approved_status = self.cleaned_data['request_status']
# Check date is not in past.
if due_date < datetime.date.today():
raise ValidationError(_('Invalid date - renewal in past'))
if due_date > datetime.date.today() + datetime.timedelta(weeks=4):
raise ValidationError(_('Invalid date - renewal more than 4 weeks ahead'))
return due_date, approved_status
views.py:
#permission_required('catalog.can_mark_returned')
def update_key_request(request, pk):
"""
View function for renewing a specific keyInstance by admin
"""
key_inst=get_object_or_404(KeyInstance, pk=pk)
# If this is a POST request then process the Form data
if request.method == 'POST':
# Create a form instance and populate it with data from the request (binding):
form = UpdateKeyRequestForm(request.POST)
# Check if the form is valid:
if form.is_valid():
# process the data in form.cleaned_data as required (here we just write it to the model due_back field)
request_status = form.cleaned_data['request_status']
due_date = form.cleaned_data['due_date']
if request_status == 'a':
key_inst.due_back = due_date
key_inst.status = 'o'
key_inst.date_out = datetime.date.today()
key_inst.save()
else:
key_inst.status = 'a'
key_inst.save()
# redirect to a new URL:
return HttpResponseRedirect(reverse('all-available-keys'))
else:
default_due_date = datetime.date.today() + datetime.timedelta(weeks=3)
form = UpdateKeyRequestForm(initial={'due_date': default_due_date})
#If this is a GET (or any other method) create the default form.
return render(request, 'catalog/roomkey_request_update.html', {'form': form, 'keyinst': key_inst})
But the thing is I implemented this exact logic with the exact same method to update the due date and it's still working fine. I am very confused as to why this one throws an error. Can anyone please shed some light?
Here is my local var on the error page:
due_date
(datetime.date(2018, 6, 13),)
form
<UpdateKeyRequestForm bound=True, valid=True, fields=(due_date)>
key_inst
<KeyInstance: Conestogo River (3fc9458a-5ec8-46a3-a04e-6a48e650b15f)>
pk
UUID('3fc9458a-5ec8-46a3-a04e-6a48e650b15f')
request
<WSGIRequest: POST '/catalog/key/3fc9458a-5ec8-46a3-a04e-6a48e650b15f/update'>
Your clean_due_date method should only return the due_date.
def clean_due_date(self):
...
return due_date
You don't appear to use approved_status in that method, so I would remove this line from the method
approved_status = self.cleaned_data['request_status']
If you do need to clean fields that rely on each other, then you should do that in the clean() method instead of a clean_<fieldname> method.

Can I use same Form twice for django SessionWizard?

I am trying to create a SessionWizardView for a trip creation process. The trip might have one leg (one way) or two legs (round trip). Each leg has similar schema so I would like to use the same Form for both step 0 and step1, with a condition saying only to use step1 when the flight is round trip.
The problem I am having is that my "submit" button keeps loading step 0 over and over again instead of moving on to step 1 as it should for a round trip flight. (I am prepopulating each of the forms based on previously requested trip info for each leg in the get_form_initial() override). My form populates correctly for the first leg, it just populates the first leg data on every submit ad infinitum.
I could make two identical forms, but that seems like poor practice. Slightly better, I could have the Return trip form just inherit from the Outbound trip form and not make any changes to it - this is what I'll try next barring a better solution.
But, I'm really wondering if there is there a way to use the same form twice?
In my urls.py:
wizard_forms = [TripCreationForm,TripCreationForm]
urlpatterns = patterns('',
url(r'^trip/wizard/(?P<pk>\d+)$',
views.CreateTripSetView.as_view(wizard_forms,
condition_dict= {'1':show_return_trip_form}), name='admin_add_tripset')
)
in views.py:
def show_return_trip_form(wizard):
"""
Tells the CreateTripSetView wizard whether to show the return trip form
Args:
wizard:
Returns: True if this is a round trip, false if one-way
"""
cleaned_data = wizard.get_cleaned_data_for_step('0') or {}
if cleaned_data.get('total_legs') == 2:
return True
return False
class CreateTripSetView(SessionWizardView):
def get_form_initial(self, step):
"""
Populates the initial form data based on the request, route etc.
THIS IS ALWAYS FIRING FOR STEP=0 WHEN I HIT SUBMIT.
Args:
step:
Returns:
"""
initial = self.initial_dict.get(step, {})
triprequest = TripRequest.objects.filter(id=self.kwargs['pk']).first()
if triprequest is None:
return initial
initial.update({
'request_id': flight_request.id,
#other fields set on initial here
})
return initial
in forms.py:
class TripCreationForm
#field defs ex.
request_id = forms.IntegerField()
#etc.
def __init__(self, initial, *args, **kwargs):
object_data = {}
object_data['request_id'] = initial['request_id']
#etc.
super(AnywhereFlightCreationForm, self).__init__(initial=object_data, *args, **kwargs)
Edited:
So far I've been able to make this work using two subclasses of TripCreationForm but not using TripCreationForm for both.
Thanks in advance!
The wizard needs to identify them as separate steps. Maybe this would work?
wizard_forms = [
("form1", TripCreationForm),
("form2", TripCreationForm),
]

Categories

Resources