I have an auto-increment field called sequence. I only want it to increment by 1 within the topic this model is in. Usually the most would ever be is about 10. Then resets for a new topic. I would like this to be done in a save override method:
class Video(TimeStampedModel):
title = models.CharField(max_length=200)
slug = models.SlugField(max_length=200)
description = models.CharField(max_length=500)
topic = models.ForeignKey('video.VideoTopic')
sequence = models.IntegerField(default=1)
def save(self, *args, **kwargs):
# Get last value of sequence and increment by 1
top = Video.objects.order_by('-sequence')[0]
self.sequence = top.sequence + 1
super(Video, self).save()
Problem with my code here is that "top" in the save method will only get the video with the highest numbered sequence.
How do I get the topic of the video being saved from which to filter my query properly?
Get it from self.topic:
top = Video.objects.filter(topic=self.topic).order_by('-sequence')[0]
You can also use latest() instead of sorting and taking [0] out of the queryset:
top = Video.objects.filter(topic=self.topic).latest('sequence')
In this case latest() would get the single Video model instance that has the maximum sequence value for the particular topic.
See also: Overriding predefined model methods.
Related
I would like to store default values for a model instance in a related object; for example, given this code:
class Contract(models.Model):
user = models.ForeignKey(User)
product = models.ForeignKey(Product)
duration = models.IntegerField(null=True, help_text='Contract validity (days)')
template = models.ForeignKey(ContractTemplate)
class ContractTemplate(models.Model):
name = models.CharField(max_length=100)
duration = models.IntegerField(help_text='Contract validity (days)')
I would like to store objects representing different common durations like:
yearly_contract = ContractTemplate.object.create(name='yearly', duration=365)
monthly_contract = ContractTemplate.object.create(name='monthly', duration=30)
and return the default value from the linked template when the object contract does not specify the value:
contract1 = Contract.objects.create(user=foo_user, foo_product, template=monthly_contract)
# contract1.duration should return 365
contract2 = Contract.objects.create(user=foo_user, foo_product, duration=45, template=monthly_contract)
# contract2.duration should return 45
So, what is the best way to achieve something like this?
You can use a callable object as default. Which seems to be what you want:
Have a look here:
https://docs.djangoproject.com/en/3.1/ref/models/fields/#default
I have a model:
class Course(models.Model):
class Meta:
ordering = ['completion_order']
degree = models.ForeignKey(Degree, on_delete=models.CASCADE)
name = models.CharField(max_length=30, null=False, blank=False)
completion_order = models.PositiveSmallIntegerField(default=0,null=False, blank=False)
required = models.ManyToManyField('Task', default=1, blank=True, symmetrical=False)
And I have written a function to run on "m2m_changed" to automatically adjust the completion order to take into account any changes in prerequisites:
def required_changed(sender, instance, **kwargs):
degreecourses = Courses.objects.all().filter(degree=instance.degree)
for zcr in degreecourses: #resets the order for the all the degree's course instances
zcr.completion_order = 0
for cr in degreecourses: #assigns new completion_order values
if cr.required.count() is 0: # if no requirements sets 'completion_order' to 1
cr.completion_order = 1
else: #sets 'completion_order' to the increment of the highest (max) value of this courses's required course
mx = cr.required.all().aggregate(Max('completion_order'))
neworder = mx['completion_order__max'] + 1
cr.completion_order = neworder
cr.save()
This is run after any change in the m2m thus:
m2m_changed.connect(required_changed, sender=Course.required.through)
I originally set this up iterating through multiple times, but then realised it was unnecessary -my (novice) question is:
Can someone explain how this is able to resolve all of the "required" values in one pass, i.e. without having to iterate through the second loop multiple times?
I'm guessing it's to do with python being object oriented - but I'm new to object oriented programming, so whilst I'm pleased it works, I'm perplexed as to how it can assign the correct running order (i.e. even with many complex interdependencies between multiple courses) with seemingly only one pass?
Whenever the user doesn't add a value, I need my Django models to replace the otherwise empty field with the value set in default.
My models looks like this:
not_before = models.TimeField(blank=True, null=True, default='00:00:00')
max_num_per_day = models.IntegerField(blank=True, null=True, default=0)
I tried every combination of null, blank and default but no matter what I do, the fields gets replaced by null instead of '00:00:00' and 0.
Is there anyway I can force it to the default value whenever the field is empty?
you can set up your form with a default function like:
class YourForm(forms.Form):
.....
def clean_field(self):
data = self.cleaned_data['not_before']
if not data:
data = '00:00:00'
or write a function in your model like:
class Molde(models.Model):
not_before = models.TimeField(blank=True, null=True, default='00:00:00')
def time(self):
if self.not_before:
return self.not_before
else:
return '00:00:00'
In this case you would call the function instead of the model field itself. You can also take a look at this.
Hope that helps.
from what I understood from your question is you just want to set it to default. you can use:
https://code.djangoproject.com/ticket/6754
don't
not_before = models.TimeField(blank=True, null=True, default='00:00:00')
instead,
import datetime
not_before = models.TimeField(default=datetime.time(0,0))
max_num_per_day = models.IntegerField(default=0)
It seems you are using a ModelForm to grab the data from the user.
In this case, the solution proposed by sasuke will not work. First, you would have to set the required param to False in your form fields, so you would stop seing those "This field is required" messages. Still, you would see errors when saving the form. Even if your model instance is initialized with the default value, the form will replace it with None, since there is an existing field in the form matching the field in the model and its value is None.
My solution is to override the values in the model instance before saving them:
model_instance = myform.save(commit=False)
if not model_instance.not_before:
model_instance.not_before = '00:00:00'
if not model_instance.max_num_per_day:
model_instance.max_num_per_day = 0
model_instance.save()
I have 2 models (sett, data_parsed), and data_parsed have a foreign key to sett.
class sett(models.Model):
setid = models.IntegerField(primary_key=True)
block = models.ForeignKey(mapt, related_name='sett_block')
username = models.ForeignKey(mapt, related_name='sett_username')
ts = models.IntegerField()
def __unicode__(self):
return str(self.setid)
class data_parsed(models.Model):
setid = models.ForeignKey(sett, related_name='data_parsed_setid', primary_key=True)
block = models.CharField(max_length=2000)
username = models.CharField(max_length=2000)
time = models.IntegerField()
def __unicode__(self):
return str(self.setid)
The data_parsed model should have the same amount of rows, but there is a possibility that they are not in "sync".
To avoid this from happening. I basically do these two steps:
Check if sett.objects.all().count() == data_parsed.objects.all().count()
This works great for a fast check, and it takes literally seconds in 1 million rows.
If they are not the same, I would check for all the sett model's pk, exclude the ones already found in data_parsed.
sett.objects.select_related().exclude(
setid__in = data_parsed.objects.all().values_list('setid', flat=True)).iterator():
Basically what this does is select all the objects in sett that exclude all the setid already in data_parsed. This method "works", but it will take around 4 hours for 1 million rows.
Is there a faster way to do this?
Finding setts without data_parsed using the reverse relation:
setts.objects.filter(data_parsed_setid__isnull=True)
If i am getting it right you are trying to keep a list of processed objects in another model by setting a foreign key.
You have only one data_parsed object by every sett object, so a many to one relationship is not needed. You could use one to one relationships and then check which object has that field as empty.
With a foreign key you could try to filter using the reverse query but that is at object level so i doubt that works.
[Update: Changed question title to be more specific]
Sorry if I didn't make the question very well, I can't figure how to do this:
class WhatEver():
number = model.IntegerField('Just a Field', default=callablefunction)
...
Where callablefunction does this query:
from myproject.app.models import WhatEver
def callablefunction():
no = WhatEver.objects.count()
return no + 1
I want to automatically write the next number, and I don't know how to do it.
I have errors from callablefunction stating that it cannot import the model, and I think there must be an easier way to do this. There's no need even to use this, but I can't figure how to do it with the pk number.
I've googled about this and the only thing I found was to use the save() method for auto incrementing the number... but I wanted to show it in the <textfield> before saving...
What would you do?
Got it! I hope this will help everyone that has any problems making a auto-filled and auto-incrementing field in django. The solution is:
class Cliente(models.Model):
"""This is the client data model, it holds all client information. This
docstring has to be improved."""
def number():
no = Cliente.objects.count()
if no == None:
return 1
else:
return no + 1
clientcode = models.IntegerField(_('Code'), max_length=6, unique=True, \
default=number)
[... here goes the rest of your model ...]
Take in care:
The number function doesn't take any arguments (not even self)
It's written BEFORE everything in the model
This was tested on django 1.2.1
This function will automatically fill the clientcode field with the next number (i.e. If you have 132 clients, when you add the next one the field will be filled with clientcode number 133)
I know that this is absurd for most of the practical situations, since the PK number is also auto-incrementing, but there's no way to autofill or take a practical use for it inside the django admin.
[update: as I stated in my comment, there's a way to use the primary key for this, but it will not fill the field before saving]
Every Django model already has an auto-generated primary key:
id = models.AutoField(primary_key=True)
It seems you are trying to duplicate an already existing behavior, just use the object primary key.
I, too, came across this problem, my instance of it was customer.number which was relative to the customers Store. I was tempted to use something like:
# Don't do this:
class Customer(models.Model):
# store = ...
number = models.IntegerField(default=0)
def save(self, *args, **kwargs):
if self.number == 0:
try:
self.number = self.store.customer_set.count() + 1
else:
self.number = 1
super(Customer, self).save(*args, **kwargs)
The above can cause several problems: Say there were 10 Customers, and I deleted customer number 6. The next customer to be added would be (seemingly) the 10th customer, which would then become a second Customer #10. (This could cause big errors in get() querysets)
What I ended up with was something like:
class Store(models.Model):
customer_number = models.IntegerField(default=1)
class Customer(models.Model):
store = models.ForeignKey(Store)
number = models.IntegerField(default=0)
def save(self, *args, **kwargs):
if self.number == 0:
self.number = self.store.customer_number
self.store.number += 1
self.store.save()
super(Customer, self).save(*args, **kwargs)
PS:
You threw out several times that you wanted this field filled in "before". I imagine you wanted it filled in before saving so that you can access it. To that I would say: this method allows you to access store.customer_number to see the next number to come.
You have errors in code, that's why you can't import it:
from django.db import models
class WhatEver(models.Model):
number = models.IntegerField('Just a Field', default=0)
and Yuval A is right about auto-incrementing: you don't even need to declare such a field. Just use the pk or id, they mean the same unless there's a composite pk in the model:
> w = Whatever(number=10)
> w
<Whatever object>
> w.id
None
> w.save()
> w.id
1
[update] Well, I haven't tried a callable as a default. I think if you fix these errors, it must work.