How does one create a DateField, which automatically increments by 1 day in the way that the pk field does?
For example, I would create a new object, this would be of 16/04/2017, the next object would be of 17/04/2017, even if they are both submitted on the same day.
How would I do this?
How about override the model's save method like this:
from datetime import datetime, timedelta
from django.db import models
class MyModel(models.Model):
date = models.DateField() # the below method will NOT work if auto_now/auto_now_add are set to True
def save(self, *args, **kwargs):
# count how many objects are already saved with the date this current object is saved
date_gte_count = MyModel.objects.filter(date__gte=self.date).count()
if date_gte_count:
# there are some objects saved with the same or greater date. Increase the day by this number.
self.date += timedelta(days=date_gte_count)
# save object in db
super().save(*args, **kwargs)
Of course, the above can be implemented using Django signals. The pre_save one.
So I worked this out using parts of Nik_m's answer and also some of my knowledge.
I essentially made a while loop which kept iterating over and adding a day, as opposed to Nik_m's answer which doesn't work after the third object due to a lack of iteration.
def save(self, *args, **kwargs):
same_date_obj = Challenge.objects.filter(date=self.date)
if same_date_obj.exists():
while True:
if Challenge.objects.filter(date=self.date).exists():
self.date += timedelta(days=1)
else:
break
super().save(*args, **kwargs)
EDIT: This answer is no longer valid, it requires a while loop and thus an indefinite amount of queries. #Nik_m's modified answer is better.
Related
I have informations about companies presented in a table. One of the field of this table is the mean value of each note the company received ('note_moyenne' in models.FicheIdentification).
By clicking on a button, people are able to submit a new note for the company ('note' in models.EvaluationGenerale). I want the mean value of the notes to update in the database each time someone submit a new note.
Here is my models.py :
class FicheIdentification(models.Model):
entreprise=models.ForeignKey(Entreprise, on_delete=models.CASCADE)
note_moyenne=models.IntegerField()
def __str__(self):
return self.entreprise.nom_entreprise
class EvaluationGenerale(models.Model):
entreprise=models.ForeignKey(Entreprise, on_delete=models.CASCADE)
note=models.IntegerField()
commentaires=models.CharField(max_length=1000)
date_evaluation=models.DateField(auto_now_add=True)
def __str__(self):
return self.commentaires
views.py :
class CreerEvaluationGenerale(CreateView):
form_class = FormulaireEvaluationGenerale
model = EvaluationGenerale
def form_valid(self, form):
form.instance.entreprise=Entreprise.objects.filter(siret=self.kwargs['siret']).first()
return super(CreerEvaluationGenerale, self).form_valid(form)
def get_success_url(self):
return reverse('details-evaluations')
Currently I just display the mean value in my table using this
def render_evaluation(self, record):
return (EvaluationGenerale.objects.filter(entreprise=record.entreprise.siret).aggregate(Avg('note'))['note__avg'])
but I really don't like this solution as I want the value to be stored in the database, in FicheIdentification.note_moyenne.
I thought about creating a UpdateView class but couldn't manage to link it with my CreateView.
Any help or documentation would be really appreciated, I'm a bit lost right know...
Typically, you would not store calculated fields. The usual way is not to store the average, but to use an annotation/aggregation in your query.
To centralize this to your model, you would want to write a custom model manager to implement this, so it can be reused anywhere you use your model without rewriting the logic.
class MyModelManager(models.Manager):
def note_average(self, **filter_kwargs):
qs = self.get_queryset()
# replace `...` with your aggregation as needed
return qs.filter(**filter_kwargs).aggregate(...)
class EvaluationGenerale(models.Model):
objects = MyModelManager() # override the default manager
# ... the rest of the model as-is
Then you can use something like the following in your view(s):
EvaluationGenerale.objects.note_average(entreprise=record.entreprise.siret)
See for additional reference: How to add a calculated field to a Django model
I see two ways of doing it.
Either a listener post_save on EvaluationGenerale (doc). You'll be able to compute the new average each time a new EvaluationGenerale is entered in DB.
#receiver(post_save, sender=EvaluationGenerale)
def evaluation_general_note_moyenne_computer_post_save_listener(sender, instance, **kwargs):
entreprise = instance.entreprise
entreprise.note_moyenne = entreprise.evaluationgeneral_set.aggregate(Avg('note')).values()[0])
entreprise.save()
post save listener will only trigger on instance.save() and models.objects.create() not on queryset.update() or model.objects.bulk_create().
Either overriding the save (doc) function of your form to compute the average after the creation of the new EvaluationGenerale
def save(self):
instance = super.save()
entreprise = instance.entreprise
entreprise.note_moyenne = entreprise.evaluationgeneral_set.aggregate(Avg('note')).values()[0]
entreprise.save()
return instance
Assuming there is as single FicheIdentification object per enterprise, you could update the note_moyenne field when you save the EvaluationGenerale object, like:
obj = FicheIdentification(...)
FicheIdentification.objects.filter(entreprise=record.entreprise.siret).update(note_moyenne=obj.aggregate(Avg('note'))['note__avg']
obj.save()
Please let me know if it works.
So, I have a model called ScheduleItem
class ScheduleItem(models.Model):
agreement = FK
location = FK
start = models.DateTimeField()
end = models.DateTimeField()
totalHours = DecimalField
def get_total_hours(self):
start = timedelta(hours=self.start.hour, minutes=self.start.minute)
end = timedelta(hours=self.end.hour, minutes=self.end.minute)
td = (end-start).seconds
totalHours=Decimal(td/Decimal(60)/Decimal(60))
return totalHours
def save(self,*args,**kwargs):
if self.pk == None:
super(ScheduleItem,self).save(self,*args,**kwargs)
self.refresh_from_db() # to access the datetime values, rather than unicode POST
self.totalHours = self.get_total_hours()
else:
self.totalHours = self.get_total_hours()
super(ScheduleItem,self).save(self,*args,**kwargs)
This throws PRIMARY key errors. I get duplicate entries with the second super(ScheduleItem,self). I cannot for the life of me figure out how to check for pk to access the datetime value and then save again within the save override method. I've tried moving things around, I've tried saving within the get_total_hours() function, with nothing but trouble.
I just want the object to be committed to the db so I can get the datetime objects and then calculate the total hours.
I'd rather not convert to datetime within the save function.
Does anyone have any tip or can anyone tell me where I'm going wrong?
You should not pass self to save(). You're calling super().save() as a bound method on an instance, so self is implicitly passed as the first argument. Change it to this:
def save(self,*args,**kwargs):
if self.pk is None:
super(ScheduleItem,self).save(*args,**kwargs)
self.refresh_from_db() # to access the datetime values, rather than unicode POST
self.totalHours = self.get_total_hours()
else:
self.totalHours = self.get_total_hours()
super(ScheduleItem,self).save(*args,**kwargs)
You get this weird behaviour because the first positional argument is force_insert, and the model instance evaluates to True. The second call to super().save() tries to force an insert with the same pk you previously saved.
ok, i need a little help here.
I have a model which has a field called slug = models.SlugField(unique=True), and i am trying to set this field on save() by appending 1 to slug if slug already exists and so on.
I want to consider race conditions.
def set_uniqslug(self, slug, i=0):
new_slug = u"{}{}".format(slug, str(i) if i else '')
try:
with transaction.atomic():
self.slug = slugify(new_slug.lower())
self.save()
return self
return self
except IntegrityError as e:
i += 1
return set_uniqslug(self, slug, i)
def save(self, *args, **kwargs):
if not self.pk:
set_uniqslug(self.name.lower()) # <--- but it does "save" above.
# i want something like:
# self.slug = self.get_uniqslug(self.name.lower())
super(Company, self).save(*args, **kwargs)
my problem is, if i call the set_uniqslug(), it needs to try to save, just to know if there is IntegrityError. in my code, it goes into infinite loop.
how can I know without saving if there is IntegrityError and then just return the unique slug back to save() method?
update:
i tried this:
with transaction.atomic():
if Company.objects.filter(slug=new_slug).exists():
i += 1
return self.set_uniqslug(slug, i)
return new_slug
it is working, but i have a stomachache by locking READ-action. am I not blocking other queries or doing any other bad stuff by doing this?
Your check-and-set version will probably not work. That will depend on your database and its implementation of the transaction isolation levels; but taking PostgreSQL as an example, the default READ COMMITTED isolation level will not prevent another transaction from inserting a row with the same slug in between your check and set.
So use your original, optimistic locking idea. As Hugo Rodger-Brown pointed out, you can avoid the infinite loop by calling the superclass's save().
Finally, you might want to consider an alternative slug format. Many times the slug will incorporate the database id (similar to StackOverflow itself, actually), which eliminates the possibility of duplicate slugs.
I want a primary key for my model to be unsigned. Therefore I do something like this:
class MyModel(models.Model):
id = models.PositiveIntegerField(primary_key=True)
This gets me an UNSIGNED column in the resulting MySQL table, which I want. However, I believe I will not get the automatic assigning to id each time I create a new object, will I? This seems to require the use of AutoField instead. Problem is, AutoField is signed. Is there a way to create an unsigned AutoField?
The actual type of the field is specified in the backend. In the case of MySQL, the backend is django.db.backends.mysql. This extract from django/db/backends/mysql/creation.py shows this translation:
class DatabaseCreation(BaseDatabaseCreation):
# This dictionary maps Field objects to their associated MySQL column
# types, as strings. Column-type strings can contain format strings; they'll
# be interpolated against the values of Field.__dict__ before being output.
# If a column type is set to None, it won't be included in the output.
data_types = {
'AutoField': 'integer AUTO_INCREMENT',
'BooleanField': 'bool',
'CharField': 'varchar(%(max_length)s)',
...
To change that, you should either monkey-patch this dict doing:
from django.db.backends.mysql.creation import DatabaseCreation
DatabaseCreation.data_types['AutoField'] = 'integer UNSIGNED AUTO_INCREMENT'
Or you create your own class, so you won't mess up with the other AutoFields:
from django.db.models.fields import AutoField
class UnsignedAutoField(AutoField):
def get_internal_type(self):
return 'UnsignedAutoField'
from django.db.backends.mysql.creation import DatabaseCreation
DatabaseCreation.data_types['UnsignedAutoField'] = 'integer UNSIGNED AUTO_INCREMENT'
And then create your own PKs:
id = UnsignedAutoField()
As it descends from AutoField, it will inherit all of its behavior.
Edit: Just to be clear, neither of the solutions written by myself or Simanas should be used in real world projects. I wrote this as an example in which direction should one go if they'd decided to avoid DBMS built-in way, and not as a completed model ready to be used.
I am sorry for writing an answer instead of a comment on the post made by Simanas, but I do not have high reputation to post one, and I feel it's needed as this question is pretty high ranked on 'django autofield unsigned integer' keywords.
Using his method is not reliable as it will produce an existing integer for new row if one of the previous objects gets deleted. Here's a modified one:
from django.db import IntegrityError
import re
class MyModel(models.Model):
def next_id():
try:
# Find the ID of the last object
last_row = MyModel.objects.order_by('-id')[0]
return last_row.id + 1
except IndexError:
# No objects exist in database so far
return 1
id = models.PositiveIntegerField(primary_key=True, default=next_id)
def save(self, *args, **kwargs):
while True:
try:
super(MyModel, self).save(*args, **kwargs)
break
except IntegrityError, e:
if e.args[0] == 1062:
if re.match("^Duplicate entry \'.*\' for key \'%s\'$"
% re.escape(self._meta.pk.name), e.args[1]):
self.id = next_id()
else:
raise
While this would work, it wouldn't know whether newly assigned ID was previously used for another objects (in case of deletion of newest objects?) and may lead to collisions in such cases; but it will work cross-database compared to Augusto's answer, which is MySQL specific.
Another caveat to this method is that if you have another application hooking to the same database, it'll have to provide the ID on INSERTs, as auto incremental is not done at database level.
You almost certainly don't want to do it this way.
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.