Creating django models in the right way - django

I have django models as follows :
class Type(models.Model):
limit = models.IntegerField()
name = models.CharField(max_length=50)
default = models.BooleanField()
def __str__(self):
return self.name
class Subscription(models.Model):
started = models.DateField()
type = models.OneToOneField(Type)
def __str__(self):
return self.type.name
class Member(models.Model):
user = models.ForeignKey(to=User)
number = models.CharField(max_length=10)
address = models.CharField(max_length=255)
postcode = models.CharField(max_length=10)
city = models.CharField(max_length=100)
active = models.BooleanField()
subscription = models.OneToOneField(Subscription, on_delete=models.CASCADE)
def __str__(self):
return self.name
Thus, member and subscription need to be OneToOneField relation, and subscription and type also OneToOneField relation.
But I want that member gets subscription of type default=true, if he creates a new account.
This is maybe not the question as it should be, but however, I would like to hear advice.
Thanks.

There are generally two options:
Attach some function to Member post_save signal, in function create appropriate instances if it was called with created=True:
from django.db.models.signals import post_save
from django.dispatch.dispatcher import receiver
#receiver(post_save, sender='myapp.Member')
def create_default_subscription(sender, instance, created, raw, using, update_fields):
if created:
<here create your subscription>
Override Member.save() method and create appropriate instances if self.pk is None.
def save(self, *args, **kwargs):
if self.pk is None:
<here create your subscription>
super().save(*args, **kwargs)
In both cases, you need to remember to associate created Subscription with your new Member.

Related

How to override "Cannot asign must be an instance" in Django?

I have two models:
class Message(models.Model):
username = models.CharField(max_length=255)
room = models.CharField(max_length=255)
content = models.TextField()
date_added = models.DateTimeField(auto_now_add=True)
class Notification(models.Model):
notification_author = models.ForeignKey(Profile, on_delete=models.CASCADE, related_name="notifauthor")
notification_from = models.ForeignKey(Order, on_delete=models.CASCADE, related_name="notiffrom")
is_read = models.BooleanField(default=False)
signals:
#receiver(post_save, sender=Message)
def create_user_notification(sender, instance, created, **kwargs):
if created:
Notification.objects.create(
notification_author=instance.username,
notification_from=instance.room)
#receiver(post_save, sender=Message)
def save_user_notification(sender, instance, **kwargs):
instance.username.save()
I am trying to create signal for creating notification after message is created. But have error:
Cannot assign "20": "Notification.notification_author" must be a "Profile" instance.
How to override it (if possible) without changing CharField to FK in Message model?
Found smth about eval(), but it does not still work
The question is how to convert the string to Profile model without
using FK
notification_author=Profile.objects.get_or_create(username=instance.username)

Django aggregate sum of manytomany is adding up everything in its field instead of the ones selected

2 Classes involved in question class Appointment and class Service
appointmentApp.models class Service
class Service(models.Model):
service_name = models.CharField(max_length=15, blank=False)
service_time = models.IntegerField(blank=False)
def __str__(self):
return self.service_name
class Meta:
verbose_name_plural = "Services"
appointmentApp/models.py class Appointment
class Appointment(models.Model):
service_chosen = models.ManyToManyField(Service, blank=False)
total_time = models.IntegerField(blank=False, null=False, default=0)
#will add up the amount of time needed for each service
def save(self, *args, **kwargs):
self.total_time += Service.objects.all().aggregate(total_time=Sum('service_time'))['total_time']
super(Appointment, self).save(*args, **kwargs)
def __str__(self):
return self.client_dog_name
Services are chosen through a multiplechoice field and on save the service_chosen's service_time are added up
but what my save function is doing instead is adding up all the existing service.service_time instead of the ones selected, why is this happening?
ManyToManyFields are saved after the containing instance is saved, you need to create a signal handler to perform this update on m2m_changed
from django.db.models.signals import m2m_changed
class Appointment(models.Model):
...
def service_chosen_changed(sender, instance=None, action=None, **kwargs):
if action == 'post_add':
instance.total_time = instance.service_chosen.aggregate(total_time=Sum('service_time'))['total_time']
instance.save()
m2m_changed.connect(service_chosen_changed, sender=Appointment.service_chosen.through)

Django model - set default charfield in lowercase

How to set default charfield in lowercase?
This is my model:
class User(models.Model):
username = models.CharField(max_length=100, unique=True)
password = models.CharField(max_length=64)
name = models.CharField(max_length=200)
phone = models.CharField(max_length=20)
email = models.CharField(max_length=200)
def __init__(self, *args, **kwargs):
self.username = self.username.lower()
I tried the __init__ but it doesn't work. I want to make the username in lowercase every time new record saved. Thanks.
While overwriting save() method is a valid solution. I found it useful to deal with this on a Field level as opposed to the Model level by overwriting get_prep_value() method.
This way if you ever want to reuse this field in a different model, you can adopt the same consistent strategy. Also the logic is separated from the save method, which you may also want to overwrite for different purposes.
For this case you would do this:
class NameField(models.CharField):
def get_prep_value(self, value):
return str(value).lower()
class User(models.Model):
username = models.CharField(max_length=100, unique=True)
password = models.CharField(max_length=64)
name = NameField(max_length=200)
phone = models.CharField(max_length=20)
email = models.CharField(max_length=200)
Just do it in the save method. ie, override the save method of Model class.
def save(self, *args, **kwargs):
self.username = self.username.lower()
return super(User, self).save(*args, **kwargs)
signals also works
from django.db.models.signals import pre_save
#receiver(pre_save, sender=YourModel)
def to_lower(sender, instance=None, **kwargs):
instance.text = instance.text.lower() if \
isinstance(instance.text, str) else ''
In my case I had a recipient_name field that I needed to make all lower case when it is stored on DB
class LowerField(models.CharField):
def get_prep_value(self, value):
return str(value).lower()
class Recipients(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='recipients', on_delete=models.CASCADE, )
recipient_account_number = models.IntegerField()
recipient_name = LowerField(max_length=30)
recipient_bank_name = models.CharField(max_length=30)
date = models.DateTimeField(auto_now=True, verbose_name='Transaction Date')
class Meta:
ordering = ['-date']
def __str__(self):
return self.recipient_name
def get_absolute_url(self):
return reverse('recipient-detail', kwargs={'pk': self.pk})
Similarly, you can apply to another table called Transactions in your app, like this
class Transactions(models.Model):
transaction_type = (
('transfer', 'Transfer'),
)
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='transactions', on_delete=models.CASCADE, )
bank_name = LowerField(max_length=50)
def save(self, force_insert=False, force_update=False):
self.YourFildName = self.YourFildName.upper()
super(YourFomrName, self).save(force_insert, force_update)

Post_save signal implementation in Django

I have models like this:
class Devices(models.Model):
name = models.CharField(max_length=255, blank=True)
uniqueid = models.CharField(db_column='uniqueid'.lower(), max_length=255, blank=True) # Field name made lowercase.
latestposition = models.ForeignKey('Positions', db_column='latestPosition_id'.lower(), blank=True, null=True) # Field name made lowercase.
class Meta:
db_table = 'devices'
verbose_name = 'Devices'
verbose_name_plural = verbose_name
def __unicode__(self):
return '%s' %(self.name)
# Call the signal to create user device when the device is created.
dispatcher.connect(save_user_device, signal=post_save, sender=Devices)
class UsersDevices(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
devices = models.ForeignKey('Devices')
class Meta:
db_table = 'users_devices'
verbose_name = 'User Devices'
verbose_name_plural = verbose_name
def __unicode__(self):
return '%s %s' %(self.user, self.devices)
When the Devices is created, I want to create users devices. user field in the UsersDevices would be signed in user who created device and devices would be the device that was just created.
def save_user_device(sender, instance, **kwargs):
## Problem is here
instance.UsersDevices.create( )
How can I create a UsersDevices using this signal with the user instance and device instance
You don't really need signals in this case. Overwrite the save() method of the model:
def save(self, *args, **kwargs):
# Call the original save function to actually save the model:
super(Devices, self).save(*args, **kwargs)
# Now this model is saved, so we can create the UsersDevices
UserDevices(user=get_user_from_somewhere(), devices=self).save()
See the documentation for more information about overwriting the save() method:
https://docs.djangoproject.com/en/1.7/topics/db/models/#overriding-model-methods
It is better to redefine save method of Devices model for this purpose. But if you want to use signals it might be done like this:
def save_user_device(sender, instance, **kwargs):
## Problem is here
UsersDevices.create(devices=instance, user=instance.user)
In this case you have to add 'user' field to Devices model.
And small tip: you should give names to models singularly, plural names is bad style.

Many-to-many relation on User table in Django

I'm writing an app where I need to associate data with user pairs. For instance, each user pair will have a compatibility score associated with them, as well as many-to-many relationships such as artists that they have in common. I'm confused about the best way to do this, it seems like I would use a combination of 1) extending User via the one-to-one relationship, 2) using a recursive relationship to self on the User table, 3) coupled with specifying extra fields on M2M relationships, but I can't wrap my head around what the model would look like.
This is how I am accomplishing this currently, which I assume is not the best way to do it as it requires two passes through the DB for each query:
in models.py (psuedo-code, assume there is an Artist class):
class UserProfile(models.Model):
user = models.OneToOneField(User)
zipcode = models.CharField(max_length=16)
def create_user_profile(sender, instance, created, **kwargs):
if created:
profile, created = UserProfile.objects.get_or_create(user=instance)
post_save.connect(create_user_profile, sender=User)
class Score(models.Model):
user = models.ForeignKey(User, related_name='score_first_user')
second_user = models.ForeignKey(User, related_name='score_second_user')
dh_score = models.DecimalField(decimal_places=2, max_digits=5)
cre_date = models.DateTimeField(auto_now_add=True)
upd_date = models.DateTimeField(auto_now=True)
deleted = models.BooleanField()
class Meta:
unique_together = ('user', 'second_user')
class UserArtist(models.Model):
user = models.ForeignKey(User, related_name='userartist_first_user')
second_user = models.ForeignKey(User, related_name='userartist_second_user')
artist = models.ForeignKey(Artist)
cre_date = models.DateTimeField(auto_now_add=True)
upd_date = models.DateTimeField(auto_now=True)
deleted = models.BooleanField()
then in views.py I save scores and common artists using something like (pseudo-code):
s = Score(user=u, second_user=second_user score=dh_score)
s.save()
and retrieve them using something like:
u = User.objects.get(username="%s" % username)
user_scores = Score.objects.filter( Q(user=u.id) | Q(second_user=u.id) ).order_by('-dh_score')[:10]
for user_score in user_scores:
# non-relevant logic to determine who is user and who is partner
...
partner_artists = UserArtist.objects.filter( (Q(user=u.id) & Q(second_user=partner.id))\
| (Q(user=partner.id) & Q(second_user=u.id))
)
What is the best way to accomplish this?
Here is how I accomplished the user-to-user data pairing, as well as making a M2M relationship to the intermediate table:
models.py
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save
class UserProfile(models.Model):
user = models.OneToOneField(User)
pair = models.ManyToManyField('self', through='PairData', symmetrical=False)
def __unicode__(self):
return "%s's profile" % self.user
def create_user_profile(sender, instance, created, **kwargs):
if created:
profile, created = UserProfile.objects.get_or_create(user=instance)
post_save.connect(create_user_profile, sender=User)
class PairData(models.Model):
first_user = models.ForeignKey(UserProfile, related_name='first_user')
second_user = models.ForeignKey(UserProfile, related_name='second_user')
raw_score = models.DecimalField(decimal_places=4, max_digits=9)
dh_score = models.DecimalField(decimal_places=2, max_digits=5)
distance = models.PositiveIntegerField()
cre_date = models.DateTimeField(auto_now_add=True)
def __unicode__(self):
return u"%s %s %f %d" % (self.first_user, self.second_user, self.dh_score, self.distance)
class Artist(models.Model):
pair = models.ManyToManyField(PairData)
artist_name = models.CharField(max_length=256)
def __unicode__(self):
return u"%s" % self.artist_name
Here is an example of how I queried the pair data (views.py):
def matches(request, username):
user_profile = User.objects.get(username=username).get_profile()
pd = PairData.objects.filter( Q(first_user=user_profile) | Q(second_user=user_profile) ).order_by('-dh_score')
and the artists associated with each pair:
def user_profile(request, username):
user_profile = User.objects.get(username=username).get_profile()
viewers_profile = request.user.get_profile()
pair = PairData.objects.filter( (Q(first_user=user_profile) & Q(second_user=viewers_profile)) \
| (Q(first_user=viewers_profile) & Q(second_user=user_profile)) )
artists = Artist.objects.filter(pair=pair)
If there is a better way to query without using Q, please share!