Copy value from different model when DjangoAdmin creates object - django

I have two models:
class Object(models.Model):
object = models.CharField()
price = models.DecimalField()
class History(models.Model):
date = models.DateTimeField()
object= models.ForeignKey(Object)
price = models.DecimalField()
When I create a new history entry, I want to copy actual price from Object to History model.
How can I do that? Need I to use specific method in view?

You can use Django's post-save signal:
Create signal receiver:
# models.py
def history_add_price(sender, instance, **kwargs):
if not instance.price and instance.object and instance.object.price:
instance.price = instance.object.price
instance.save()
return True
Register signal:
# models.py
from django.db import models
models.signals.post_save.connect(history_add_price, sender=History, dispatch_uid="add_price_post_save", weak=False)

Related

New model using signals

models.py
class Customer(models.Model):
name = models.CharField(max_length=16)
description = models.CharField(max_length=32)
Is it possible to have new model using signals .
For example if i save above model it must give me below model.
def create_customer(sender, instance, created, **kwargs):
class Newname(models.Model):
customername = models.CharField(max_length=100)
signals.post_save.connect(receiver=create_customer, sender=Customer)
def create_customer(sender, instance, created, **kwargs):
Newname.objects.create(customername=instance.name)
This creates a Newname object and saves it to the database. The instance in this case would be the Customer object that you just saved.

Creating a unique object using DateField()

I would just like to create a model that essentially prevents the same date being selected by two different users (or the same user).
E.g if User1 has selected 2019-01-10 as a "date" for a booking, then User2 (or any other Users) are not able to create an object with that same date.
I have created a very basic model that can allow different Users to create an object using the DateField(). Using the Django admin page, I can create different instances of objects by two different Users (admin and Test_User).
In order to try to ensure that a new object can't be created if that date has already been used by a different object I have tried the following approach:
a compare function that utilizes __dict__.
models.py
from __future__ import unicode_literals
from django.db import models, IntegrityError
from django.db.models import Q
from django.contrib.auth.models import User
from datetime import datetime
class Booking(models.Model):
date = models.DateField(null=False, blank=False)
booked_at = models.DateTimeField(auto_now_add=True)
booking_last_modified = models.DateTimeField(auto_now=True)
class PersonalBooking(Booking):
user = models.ForeignKey(User, on_delete=models.CASCADE)
def compare(self, obj):
excluded_keys = 'booked_at', '_state', 'booking_last_modified', 'user',
return self._compare(self, obj, excluded_keys)
def _compare(self, obj1, obj2, excluded_keys):
d1, d2 = obj1.__dict__, obj2.__dict__
for k,v in d1.items():
if k in excluded_keys:
continue
try:
if v != d2[k]:
pass
except IntegrityError as error:
print(error)
print('Date already selected by different User. Please select another date.')
admin.py
from django.contrib import admin
from . import models
from .models import Booking, PersonalBooking
class PersonalBookingAdmin(admin.ModelAdmin):
list_display = ('format_date', 'user', )
def format_date(self, obj):
return obj.date.strftime('%d-%b-%Y')
format_date.admin_order_field = 'date'
format_date.short_description = 'Date'
def user(self, obj):
return obj.user()
user.admin_order_field = 'user'
user.short_description = 'User'
admin.site.register(models.PersonalBooking, PersonalBookingAdmin)
It didn't work as I had hoped, objects with the same date could still be created by the same or different users. Perhaps there is a simpler way? Or maybe I need to use the Q() class? I am not very familiar with it.
Any help is greatly appreciated.
Thanks.
You could do this validation at the database level by setting the unique attribute to True in your model's field.
class Booking(models.Model):
date = models.DateField(null=False, blank=False, unique=True)
booked_at = models.DateTimeField(auto_now_add=True)
booking_last_modified = models.DateTimeField(auto_now=True)
But this would present issues if the field was changed later to store time.
If you are going to be storing the time as well, you could override the model's default save function to check that there isn't another Booking with the same date (__date) each time it is saved. exists() returns True if there is a match, so this will throw a ValidationError if there is a match.
from django.core.exceptions import ValidationError
class Booking(models.Model):
date = models.DateTimeField(null=False, blank=False)
booked_at = models.DateTimeField(auto_now_add=True)
booking_last_modified = models.DateTimeField(auto_now=True)
def save(self, *args, **kwargs):
# Make sure there are no bookings on the same day
if Booking.objects.exclude(pk=self.pk).filter(date__date=self.date.date).exists():
raise ValidationError('There cannot be two bookings with the same date.')
super(Booking, self).save(*args, **kwargs)
Try this
https://docs.djangoproject.com/en/2.1/ref/models/fields/#unique-for-date
For user column set unique_for_date=True

how to test a model that has a foreign key in django?

I'm using python 3.5 and Django 1.10 and trying to test my app in tests.py, but an error appeared, it said: ValueError: Cannot assign "1": "NewsLetter.UserID" must be a "User" instance. so how to test a fk value here?
here is the code:
class NewsletterModelTest(TestCase):
#classmethod
def setUpTestData(cls):
#Set up non-modified objects used by all test methods
NewsLetter.objects.create(NewsLetterID=1, Email='test#test.com', Connected=False,UserID=1)
class NewsLetter(models.Model):
NewsLetterID = models.AutoField(primary_key=True)
Email = models.CharField(max_length=255)
Connected = models.BooleanField(default=False)
UserID = models.ForeignKey(User, on_delete=models.CASCADE)
class Meta:
db_table = 'NewsLetter'
In your setupTestData method you have to create a User object, and pass it into the NewsLetter object create method.
#classmethod
def setUpTestData(cls):
#Set up non-modified objects used by all test methods
user = User.objects.create(<fill params here>)
NewsLetter.objects.create(NewsLetterID=1, Email='test#test.com', Connected=False,UserID=user)
For those who land here.
To write a test for a model that has a ForeignKey field, you need to create an instance of the model that the ForeignKey points to and then call save() on the ForeignKey instance, before applying it to the creation of your target model for the test.
eg. (simplified for brevity)
class BookTestCase(TestCase):
def test_fields_author_name(self):
author = Author(name="Mazuki Sekida")
author.save()
book = Book(name="Zen Training", author=author)
book.save()
# assertion example ...
record = Book.objects.get(id=1)
self.assertEqual(record.author.name, "Mazuki Sekida")
Very similar to what #Arpit Solanki answered, here's what I did:
from datetime import date
from django.test import TestCase
from ..models import Post, Author
class PostModelTest(TestCase):
#classmethod
def setUpTestData(cls):
cls.author_ = 'Rambo'
cls.author = Author.objects.create(name=cls.author_)
cls.post = Post.objects.create(
title='A test', author=cls.author, content='This is a test.', date=date(2021, 6, 16))
def test_if_post_has_required_author(self):
self.assertEqual(self.post.author.name, self.author_)

Django foreign key on_delete: how to pass argument to callable when using models.SET

This is my models.py:
def set_image(instance):
return Image.objects.filter(user=instance.user)[0]
class User(models.Model):
user = models.CharField(max_length=50)
class Image(models.Model):
user = models.ForeignKey(User)
image = models.ImageField(upload_to='media')
class Item(models.Model):
user = models.ForeignKey(User)
image = models.ForeignKey(
Image,
blank=True,
null=True,
on_delete=models.SET(set_image)
)
When I delete an 'Image', it's related 'Item' calls set_image, which returns an error, because models.SET doesn't pass the instance to the callable. How can I change this behavior? Should I override models.SET or is there any other way around it?
Override the Image delete method to remove the Item ForeignKey
The following can be used as an intermediary model base class to add this functionality to all of your models:
from django.db import models
class Model(models.Model):
"""
Intermediate model base class.
"""
class Meta:
abstract = True
def delete(self, *args, **kwargs):
self.clear_nullable_related()
super(Model, self).delete(*args, **kwargs)
def clear_nullable_related(self):
"""
Recursively clears any nullable foreign key fields on related objects.
Django is hard-wired for cascading deletes, which is very dangerous for
us. This simulates ON DELETE SET NULL behavior manually.
"""
for related in self._meta.get_all_related_objects():
accessor = related.get_accessor_name()
related_set = getattr(self, accessor)
if related.field.null:
related_set.clear()
else:
for related_object in related_set.all():
related_object.clear_nullable_related()
source

Django - Create object and sub objects

I have the following code:
class Album(models.Model):
name = models.CharField(max_length=255, unique=True, null=False)
rating = models.ForeignKey("Rating", null=False)
class Rating(models.Model):
value = models.IntegerField(null=False, default=0)
What is the best way (in the django/python philosophy) to create an object (Album) and it's sub object (Rating) and save it?
I have done this:
a = Album()
a.name = "..."
r = Rating()
r.save()
a.rating = r
a.save()
I don't like this because the part of creating the sub object empty is totally not useful.
I'd prefer some simple way like this - the sub-object should be created automatically:
a = Album()
a.name = "..."
a.save()
You'll want to look into signals.
Essentially a signals are sent when an Object is saved.
Using a pre_save signal you can then create a Rating and associate it to the new Album jsut before it is saved for the first time.
from django.db.models.signals import pre_save
from django.dispatch import receiver
from myapp.models import Album, Rating
#receiver(pre_save, sender=Album)
def add_rating_to_album(sender, **kwargs):
# If saving a new Album
if not instance.id:
# Create and save a new rating
rating = Rating()
rating.save()
# Associate it to the Album being saved
instance.rating = rating
# Continue to normal save with new rating applied
I haven't tested this specific code but it should get you in the right direction
Using signals as rockingskier said is a nice way to do it because your Album objects does not have to know anything about Rating so it gains independence.
Another way to do it would be to override the method save of Album and create the new Rating object there, this code is based on the example in Django docs:
class Album(models.Model):
name = models.CharField(max_length=100)
def save(self, *args, **kwargs):
# do something here
super(Album, self).save(*args, **kwargs) # Call the "real" save() method.
# do anything else
This may be a simpler way to do it but your Album model would be tied to your Rating model.