Creating model instance in another app - django

I have a System model in one app that I'd like to connect to a Queue model in another app (django-helpdesk). If no Queue object is selected for a System object, I'd like to initialize a new Queue object, using information from the System to generate it.
I've covered all of the required fields for the Queue, and yet this doesn't seem to work. It doesn't throw an error; it just never generates a new Queue object. Can anyone spot the issue in question, or recommend another way of covering this?
#models.py
import helpdesk
....
class System(models.Model):
queue = models.ForeignKey(
helpdesk.models.Queue,
blank = True,
null = True,
editable = True,
verbose_name = _('Queue'),
)
def __save__(self, *args, **kwargs):
if not self.queue:
slug = slugify(self.name)
queue = helpdesk.models.Queue(title=self.name, slug=slug)
queue.save()
self.queue = queue.pk
super(System, self).save(*args, **kwargs)
EDIT: For reference, there's an additional mistake beyond using the double underscores for the save() method. The line self.queue = queue.pk should instead be self.queue = queue.

You should override save() method, not __save__(), see examples:
Django. Override save for model
How do you detect a new instance of the model in Django's model.save()

Rename:
def __save__(...)
to
def save(...)

No need for the double underscores in the save() method. That's for Python magic methods only.

Related

Add extra value from ImportForm to model instance before imported or saved

I have the following model that I want to import:
class Token(models.Model):
key = models.CharField(db_index=True,unique=True,primary_key=True, )
pool = models.ForeignKey(Pool, on_delete=models.CASCADE)
state = models.PositiveSmallIntegerField(default=State.VALID, choices=State.choices)
then a resource model:
class TokenResource(resources.ModelResource):
class Meta:
model = Token
import_id_fields = ("key",)
and a ImportForm for querying the pool:
class AccessTokenImportForm(ImportForm):
pool = forms.ModelChoiceField(queryset=Pool.objects.all(), required=True)
This value shouls be set for all the imported token objects.
The problem is, that I did not find a way to acomplish this yet.
How do I get the value from the form to the instance?
The before_save_instance and or similar methods I cannot access these values anymore. I have to pass this alot earlier I guess. Does someone ever done something similar?
Thanks and regards
Matt
It seems that you could pass the extra value in the Admin:
class TokenAdmin(ImportMixin, admin.ModelAdmin):
def get_import_data_kwargs(self, request, *args, **kwargs):
form = kwargs.get('form')
if form:
kwargs.pop('form')
if 'pool' in form.cleaned_data:
return {'pool_id' : form.cleaned_data['pool'].id }
return kwargs
return {}
And then use this data within your resources after_import_instance method to set this value
def after_import_instance(self, instance, new, row_number=None, **kwargs):
pool_id = kwargs.get('pool_id')
instance.pool_id = pool_id
instance.created_at = timezone.now()
Then the error from missing field (non null) is gone.

What's the approach to implement a TTL on Django model record?

Can you please tell me the approach to implement a TTL on Django model record?
For Example:Remove all values stored over more than x minutes.
The first thing you would need to do is to add a field to your model that tracks the time the record was created. Passing auto_now_add=True to a DateTimeField means that it will automatically be populated by the time the record was created
class Values(models.Model):
key = models.CharField(max_length=20)
value = models.TextField(blank='False',default='')
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.key
Then you need a function that will delete records that are older than a certain age, a classmethod seems appropriate
#classmethod
def delete_old_records(cls, minutes=5):
cut_off = datetime.datetime.now() - datetime.timedelta(minutes=minutes)
cls.objects.filter(created__lt=cut_off).delete()
Now you need to add a method for calling this function from a script, a custom management command is ideal
class Command(BaseCommand):
help = 'Deletes old Values'
def handle(self, *args, **options):
Values.delete_old_records()
Then you need to schedule this command, something like cron or celerybeat would be good for this

Django custom save and update

I have a custom save method for a model.
class Ticket(models.Model):
show = models.ForeignKey(Show)
seat = models.ForeignKey(Seat)
ref = models.CharField(max_length=100)
paid = models.BooleanField(default=False)
class Meta:
unique_together = ('show', 'seat')
def save(self, *args, **kwargs):
if self.paid:
do_something()
In the view I would like to update multiple Ticket objects:
Ticket.objects.filter(ref='ref').update(paid=True)
But, since this won't call the custom save method. The method do_something() won't be processed. Is there any way to solve this problem?
The obvious solution would be:
for ticket in Ticket.objects.filter(ref='ref'):
ticket.paid = True
ticket.save()
If you are doing the update for performance reasons you don't want to give up, you could do:
new_paid_tickets = Ticket.objects.filter(ref='ref')
new_paid_tickets.update(paid=True)
for ticket in new_paid_tickets:
do_something()
def save(self, *args, **kwargs):
if self.paid:
do_something()
super(Ticket, self).save(*args, **kwargs)
use this and add the custom code inside self.paid, hope it helps
Quote from docs:
Be aware that the update() method is converted directly to an SQL statement. It is a bulk operation for direct updates. It doesn’t run any save() methods on your models, or emit the pre_save or post_save signals (which are a consequence of calling save()), or honor the auto_now field option. If you want to save every item in a QuerySet and make sure that the save() method is called on each instance, you don’t need any special function to handle that. Just loop over them and call save().
So you need to iterate over queryset and call save() method for each element.

Django call function when an object gets added

Hay, i have a simple model
class Manufacturer(models.Model):
name = models.CharField()
car_count = models.IntegerField()
class Car(models.Model):
maker = ForeignKey(Manufacturer)
I want to update the car_count field when a car is added to a manufacturer, I'm aware i could just count the Manufacturer.car_set() to get the value, but i want the value to be stored within that car_count field.
How would i do this?
EDIT
Would something like this work?
def save(self):
if self.id:
car_count = self.car_set.count()
self.save()
The best way make something happen when a model is saved it to use a signal. Django's documentation does a good job of describing what signals are and how to use them: http://docs.djangoproject.com/en/dev/topics/signals/
I'm not sure why you need to make it a field in the model though. Databases are very good at counting rows, so you could add a model method to count the cars which would use a very fast COUNT() query.
class Manufacturer(models.Model):
name = models.CharField()
def car_count(self):
return Car.objects.filter(maker=self).count()
class Car(models.Model):
maker = ForeignKey(Manufacturer)
In light of the requirement added by your comment, you're back to updating a field on the Manufacturer model whenever a Car is saved. I would still recommend using the count() method to ensure the car_count field is accurate. So your signal handler could look something like this:
def update_car_count(sender, **kwargs):
instance = kwargs['instance']
manufacturer = instance.maker
manufacturer.car_count = Car.objects.filter(maker=self).count()
manufacturer.save()
Then you would connect it to both the post_save and post_delete signals of the Car model.
post_save.connect(update_car_count, sender=Car)
post_delete.connect(update_car_count, sender=Car)
The proper way to let the database show how many cars a manufacturer has, is to let the database calculate it in the view using aggregations.
from django.db.models import Count
Manufacturer.objects.all().annotate(car_count=Count(car)).order_by('car_count')
Databases are very efficient at that sort of thing, and you can order by the result as seen above.
I'm a tiny bit confused.
.. when a car is added to a manufacturer ..
In the code shown in your question, I'd guess, you save a car with some manufacturer, e.g.
car.maker = Manufacturer.objects.get(name='BMW')
car.save()
Then the save method of the Car class would need to update the car_count of the manufacturer (see Overriding predefined model methods for more details).
def save(self, *args, **kwargs):
if self.id:
self.maker.car_count = len(self.maker.car_set.all())
super(Car, self).save(*args, **kwargs)
Since this isn't the most elegant code, I'd suggest as #Josh Wright to look into signals for that matter.
P.S. You could also add a method on the Manufacturer class, but I guess, you want this attribute to live in the database.
class Manufacturer(models.Model):
name = models.CharField()
def _car_count(self):
return len(self.car_set.all())
car_count = property(_car_count)
...
The override in MYYN's answer won't work, since Car.id won't be set (and probably not included in the Manufacturer's car_set) until it's saved. Instead, I'd do something like:
def save(self, *args, **kwargs):
super(Car, self).save(*args, **kwargs)
self.maker.car_count = len(self.maker.car_set.all())
self.maker.save()
Which is untested, but should work.
Of course, the best way is to use Josh's solution, since that's going 'with the grain' of Django.

Define an attribute in a model, like an object of the other model in Django

Is posible to define an attribute in a data model like an object of other data model in Django?
This is the scenary:
models.py
class Inmueble(models.Model):
calle = models.CharField(max_length=20, verbose_name="Calle")
numero = models.CharField(max_length=6, verbose_name="Numero")
piso = models.IntegerField(verbose_name="Piso", blank=True, null=True)
galeria_id = models.OneToOneField(Galeria, verbose_name="Galería del Inmueble")
class Galeria(Gallery):
nombre = models.CharField(max_length=30, verbose_name="Nombre")
The point is: I need to create a new Galeria object automatically every time an Inmueble object is created. Thanks in advance!
Analía.
There are two ways to handle this:
Override the save() method for the Inmueble model.
Create a signal handler on Galeria that receives signals emitted by Inmueble
Both methods would work and are acceptable, however I recommend using a signal for a couple reasons:
It's a bit more de-coupled. If later you change or remove Galeria, your code doesn't break
The signal handler for postsave includes a boolean value to indicate whether the model is being created or not. You could technically implement the same functionality in model save() by checking if the model has a .id set or not, but IMO the signal is a cleaner solution.
Here's an idea of the code for both of these...
Using a Signal (recommended)
from django.db.models.signals import post_save
from wherever.models import Inmueble
class Galeria(Gallery):
# ...
def inmueble_postsave(sender, instance, created, **kwargs):
if created:
instance.galeria_id = Galeria.objects.create()
instance.save()
post_save.connect(inmueble_postsave, sender=Inmueble, dispatch_uid='galeria.inmueble_postsave')
Overriding Model save() Method
from wherever.models import Galeria
class Inmueble(models.Model):
# ...
def save(self, force_insert=False, force_update=False):
# No Id = newly created model
if not self.id:
self.galeria_id = Galeria.objects.create()
super(Inmueble, self).save()
Maybe
AutoOneToOneField is the answer.
Finally, I did:
from django.db.models.signals import post_save
class Galeria(Gallery):
inmueble_id = models.ForeignKey(Inmueble)
def inmueble_postsave(sender, instance, created, **kwargs):
if created:
instance = Galeria.objects.create(inmueble_id=instance, title=instance.calle+' '+instance.numero, title_slug=instance.calle+' '+instance.numero)
instance.save()
post_save.connect(inmueble_postsave, sender=Inmueble, dispatch_uid='galeria.inmueble_postsave')