django DateField model -- unable to find the date differences - django

I'm unable to find the difference between two dates in my form.
models.py:
class Testing(models.Model):
Planned_Start_Date = models.DateField()
Planned_End_Date = models.DateField()
Planned_Duration = models.IntegerField(default=Planned_Start_Date - Planned_End_Date)
difference between the date has to calculated and it should stored in the database but It doesn't works

default is a callable function that is just used on the class level, so you can't use it to do what you want. You should override the model's save() method (or better, implement a pre_save signal handler to populate the field just before the object is saved:
def save(self, **kwargs):
self.Planned_Duration = self.Planned_End_Date - self.Planned_Start_Date
super().save(**kwargs)
But why do you save a computed property to the database? This column is unnecessary. Both for querying (you can easily use computed queries on the start and end date) as for retrieving, you're wasting db space.
# if you need the duration just define a property
#property
def planned_duration(self):
return self.Planned_End_Date - self.Planned_Start_Date
# if you need to query tasks which last more than 2 days
Testing.objects.filter(Planned_End_Date__gt=F('Planned_Start_Date') + datetime.timedelta(days=2))
Note: Python conventions would recommend you name your fields using snake_case (planned_duration, planned_end_date, planned_start_date). Use CamelCase for classes (TestingTask). Don't mix the two.

Related

Is there a way in Django to create and populate a column to the database with a string's length value?

I have a class/model in my models.py that can receive a textField. I want to add a column in my database that corresponds to the length of this textField. What is the best way of doing that?
You can add a field that stores the character count value to your model:
class YourModel(models.Model):
my_text_field = models.TextField()
char_count = models.DecimalField(max_digits=3, decimal_places=0, blank=True, null=True)
And override the model save method to update the field on save:
def save(self, *args, **kwargs):
if self.my_text_field:
self.char_count = len(self.my_text_field)
super().save(*args, **kwargs)
Adjust the field options (like max_digits) to suit your needs.
It depends how you want to use the length. The answer with the model property (#property) is good if you don't want to do complex queries involving the length, such as filtering by length, ordering, etc.
The solution with an extra database field solves the problem above, but it puts on you the duty of keeping the char_count column updated. If you need the length in your queries, a better way would be to use the database LENGTH function, which can be done readily in Django without changing your model. Examples, with a Article model that has a text CharField:
Order by article text length:
from django.db.models.functions import Length
sorted_qs = Article.objects.order_by(Length('text'))
If you print str(sorted_qs.query), you can see ORDER BY LENGTH("app_article"."text") ASC in the query.
Filter by length greater than x:
Article.objects.annotate(text_len=Length('text')).filter(text_len__gte=x)
See the doc on aggregation.
If you have a large database table, consider adding an index on the length. In PostgreSQL, that would be:
CREATE INDEX app_article_text_length_idx ON app_article (LENGTH(text));
You can define a function in your model that returns length of the TextField field.
Suppose your model is like this:
class ModelName(models.Model):
str_field = models.TextField()
#property
def length(self):
return (self.str_field)
And my_model is one object of this model class, below code returns length of str_field
my_model.length
This way doesn't add a column to database table but you can access the length of str_field.

Filter models by checking one datetime's month against another

Using Django's ORM, I am trying to find instances of myModel based on two of its datetime variables; specifically, where the months of these two datetimes are not equal. I understand to filter by the value of a modelfield, you can use Django's F( ) expressions, so I thought I'd try something like this:
myModel.objects.filter(fixed_date__month=F('closed_date__month'))
I know this wouldn't find instances where they aren't equal, but I thought it'd be a good first step since I've never used the F expressions before. However, it doesn't work as I thought it should. I expected it to give me a queryset of objects where the value of the fixed_date month was equal to the value of the closed_date month, but instead I get an error:
FieldError: Join on field 'closed_date' not permitted. Did you misspell 'month' for the lookup type?
I'm not sure if what I'm trying to do isn't possible or straightforward with the ORM, or if I'm just making a simple mistake.
It doesn't look like django F objects currently support extracting the month inside a DateTimeField, the error message seems to be stating that the F object is trying to convert the '__' inside the string 'closed_date__month' as a Foreignkey between different objects, which are usually stored as joins inside an sql database.
You could carry out the same query by iterating across the objects:
result = []
for obj in myModel.objects.all():
if obj.fixed_date.month != obj.closed_date.month:
result.append(obj)
or as a list comprehension:
result = [obj for obj in myModel.objects.all() if obj.fixed_date.month != obj.closed_date.month]
Alternatively, if this is not efficient enough, the months for the two dates could be cached as IntegerFields within the model, something like:
class myModel(models.Model):
....other fields....
fixed_date = models.DateTimeField()
closed_date = models.DateTimeField()
fixed_month = models.IntegerField()
closed_month = models.IntegerField()
store the two integers when the relevant dates are updated:
myModel.fixed_month = myModel.fixed_date.month
myModel.save()
Then use an F object to compare the two integer fields:
myModel.objects.filter(fixed_month__ne=F('closed_month'))
The ne modifier will do the not equal test.
Edit - using raw sql
If you are using an sql based database, then most efficient method is to use the .raw() method to manually specify the sql:
myModel.objects.raw('SELECT * FROM stuff_mymodel WHERE MONTH(fixed_date) != MONTH(close_date)')
Where 'stuff_mymodel' is the correct name of the table in the database. This uses the SQL MONTH() function to extract the values from the month fields, and compare their values. It will return a collection of objects.
There is some nay-saying about the django query system, for example: http://charlesleifer.com/blog/shortcomings-in-the-django-orm-and-a-look-at-peewee-a-lightweight-alternative/. This example could be taken as demonstrating another inconsistency in it's query api.
My thinking is this:
class myModel(models.Model):
fixed_date = models.DateTimeField()
closed_date = models.DateTimeField()
def has_diff_months(self):
if self.fixed_date.month != self.closed_date.month:
return True
return False
Then:
[x for x in myModel.objects.all() if x.has_diff_months()]
However, for a truly efficient solution you'd have to use another column. It makes sense to me that it'd be a computed boolean field that is created when you save, like so:
class myModel(models.Model):
fixed_date = models.DateTimeField()
closed_date = models.DateTimeField()
diff_months = models.BooleanField()
#overriding save method
def save(self, *args, **kwargs):
#calculating the value for diff_months
self.diff_months = (self.fixed_date.month != self.closed_date.month)
#aaand... saving:
super(Blog, self).save(*args, **kwargs)
Then filtering would simply be:
myModel.objects.filter(diff_months=True)

Django aggregate multiple columns after arithmetic operation

I have a really strange problem with Django 1.4.4.
I have this model :
class LogQuarter(models.Model):
timestamp = models.DateTimeField()
domain = models.CharField(max_length=253)
attempts = models.IntegerField()
success = models.IntegerField()
queue = models.IntegerField()
...
I need to gather the first 20 domains with the higher sent property. The sent property is attempts - queue.
This is my request:
obj = LogQuarter.objects\
.aggregate(Sum(F('attempts')-F('queue')))\
.values('domain')\
.filter(**kwargs)\
.order_by('-sent')[:20]
I tried with extra too and it isn't working.
It's really basic SQL, I am surprised that Django can't do this.
Did someone has a solution ?
You can actually do this via subclassing some of the aggregation functionality. This requires digging in to the code to really understand, but here's what I coded up to do something similar for MAX and MIN. (Note: this code is based of Django 1.4 / MySQL).
Start by subclassing the underlying aggregation class and overriding the as_sql method. This method writes the actual SQL to the database query. We have to make sure to quote the field that gets passed in correctly and associate it with the proper table name.
from django.db.models.sql import aggregates
class SqlCalculatedSum(aggregates.Aggregate):
sql_function = 'SUM'
sql_template = '%(function)s(%(field)s - %(other_field)s)'
def as_sql(self, qn, connection):
# self.col is currently a tuple, where the first item is the table name and
# the second item is the primary column name. Assuming our calculation is
# on two fields in the same table, we can use that to our advantage. qn is
# underlying DB quoting object and quotes things appropriately. The column
# entry in the self.extra var is the actual database column name for the
# secondary column.
self.extra['other_field'] = '.'.join(
[qn(c) for c in (self.col[0], self.extra['column'])])
return super(SqlCalculatedSum, self).as_sql(qn, connection)
Next, subclass the general model aggregation class and override the add_to_query method. This method is what determines how the aggregate gets added to the underlying query object. We want to be able to pass in the field name (e.g. queue) but get the corresponding DB column name (in case it is something different).
from django.db import models
class CalculatedSum(models.Aggregate):
name = SqlCalculatedSum
def add_to_query(self, query, alias, col, source, is_summary):
# Utilize the fact that self.extra is set to all of the extra kwargs passed
# in on initialization. We want to get the corresponding database column
# name for whatever field we pass in to the "variable" kwarg.
self.extra['column'] = query.model._meta.get_field(
self.extra['variable']).db_column
query.aggregates[alias] = self.name(
col, source=source, is_summary=is_summary, **self.extra)
You can then use your new class in an annotation like this:
queryset.annotate(calc_attempts=CalculatedSum('attempts', variable='queue'))
Assuming your attempts and queue fields have those same db column names, this should generate SQL similar to the following:
SELECT SUM(`LogQuarter`.`attempts` - `LogQuarter`.`queue`) AS calc_attempts
And there you go.
I am not sure if you can do this Sum(F('attempts')-F('queue')). It should throw an error in the first place. I guess, easier approach would be to use extra.
result = LogQuarter.objects.extra(select={'sent':'(attempts-queue)'}, order_by=['-sent'])[:20]

How to give choices from values of another field in Django models

I have a form, which is for scheduling an appointment. I give user 3 dates on which the meeting can be scheduled. Now in the admin I want to select one of the dates according to my convenience, and store it in a field of the same model. How can I do that
Right now my meeting dates are just char fields like this
schedule1 = models.CharField()
schedule2 = model.CharField()
schedule3 = models.CharFiedl()
selected_schedule = model.CharField(choices={something here})
The schedule fields will be filled when the object is created. So I am sure the choices will be there, I just have to dynamically set them. How can I do this?
Any help will be appreciated.
Here's what you do (if the schedule fields are already prefilled):
class ScheduleForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ScheduleForm, self).__init__(*args, **kwargs)
instance = kwargs.get('instance', None)
if instance is not None:
self.fields['selected_schedule'].choices = (
(instance.schedule1, instance.schedule1),
(instance.schedule2, instance.schedule2),
(instance.schedule3, instance.schedule3),
)
On your admin, simply state that you want to use that form:
class TheAdminInQuestion(admin.ModelAdmin):
form = ScheduleForm
Further note:
I'd recommend a different solution to your problem than storing the choices on the same model. For instance, you might have a model called ScheduleChoice, and there could be 3 records of that, etc. Or, you might calculate the value based on some other rules, and just don't store the choices at all. Also, I'd recommend using DateTimeField to store the dates. You can convert the date to any format you like (e.g. January 12th, 2011 at 3:35PM) and still store it as the same datetime object in the database.

Django, unique time interval for the admin panel

Greetings,
Assume I have such model:
class Foo(models.Model):
type = models.ForeignKey(Type)
start_time = models.DateTimeField()
end_time models.DateTimeField()
For each Foo object that is having the same type, I need this time interval (end_time - start_time) to be unique so that creation of a second Foo with a clashing interval won't be possible. How can this be achieved ?
See the documentation about custom validation in the admin interface.
Basically you have to create your own (model) form, lets say CustomFooAdminForm and assign it to the admin model:
class FooAdmin(admin.ModelAdmin):
form = CustomFooAdminForm
and in the form you can have something like (see custom validation in forms):
# more or less pseudo code
class CustomFooAdminForm(forms.ModelForm):
def clean(self):
cleaned_data = super(CustomFooAdminForm, self).clean()
interval = cleaned_data.get("end_time") - cleaned_data.get("start_time")
type = cleaned_data.get("type")
q = Foo.objects.extra(select={'interval':'time_end - time_start'}
counter = q.filter(interval=intervak, type=type).count()
if counter > 0:
raise forms.ValidationError("ERROR!!!!")
# Always return the full collection of cleaned data.
return cleaned_data
Maybe you have to transform the DateTimeFields to UNIX timestamps, before you can subtract them in SQL (UNIX_TIMESTAMP(time_end) - UNIX_TIMESTAMP (time_start) for MySQL). Or you can use DATEDIFF() in MySQL to get the difference. But note that you tie your application to a certain database if you use such special functions (as long as they are not available in other databases under the same name).