Many-to-many relation on User table in Django - 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!

Related

How do I best restrict by user and by data model using Django?

I'm using django-guardian and I encountered some issues with the default mixins. And I want to know if there's a better way to do this.
GitHub Link: https://github.com/iaggocapitanio1/django_homepage
Problem:
If I want to limit access at both the model and object levels, using these two mixins (PermissionRequiredMixin, PermissionListMixin) is not a very easy task. Because the permissions_required attribute is overridden. To get around this I had to create a new attribute "object_permission" and do the following:
Model Looks like:
# Create your models here.
from django.db import models
from localflavor.br import models as localModels
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
pass
class Customer(models.Model):
user: User = models.OneToOneField(User, on_delete=models.CASCADE)
def __str__(self):
return f'{self.user.first_name} {self.user.last_name}'
class Company(models.Model):
user: User = models.OneToOneField(User, on_delete=models.CASCADE)
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='comapnies')
def __str__(self):
return f'{self.user.first_name} {self.user.last_name}'
class Project(models.Model):
name = models.CharField(max_length=100)
owner = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='projects')
class Meta:
permissions = (('read_project', 'Read Project'),)
def __str__(self):
return self.name
class House(models.Model):
rooms = models.IntegerField()
postal_code = localModels.BRPostalCodeField()
project = models.ForeignKey(Project, on_delete=models.CASCADE)
Here I needed to create a new attribute ("object_permission") to limit object-level access
in the View:
class ProjectsListView(PermissionRequiredMixin, PermissionListMixin, ListView):
template_name = 'home/projects.html'
model = models.Project
permission_required = ["homepage.view_project"]
object_permission = ["read_project"]
redirect_field_name = 'next'
login_url = 'login/'
get_objects_for_user_extra_kwargs = {}
def get_object_permission(self, request: HttpRequest = None) -> List[str]:
if isinstance(self.object_permission, str):
perms = [self.object_permission]
elif isinstance(self.object_permission, Iterable):
perms = [p for p in self.object_permission]
else:
raise ImproperlyConfigured("'PermissionRequiredMixin' requires "
"'permission_required' attribute to be set to "
"'<app_label>.<permission codename>' but is set to '%s' instead"
% self.permission_required)
return perms
def get_get_objects_for_user_kwargs(self, queryset):
return dict(user=self.request.user,
perms=self.get_object_permission(self.request),
klass=queryset,
**self.get_objects_for_user_extra_kwargs)
#receiver(post_save, sender=models.Project)
def project_post_save(sender, **kwargs):
"""
Create a Profile instance for all newly created User instances. We only
run on user creation to avoid having to check for existence on each call
to User.save.
"""
project: models.Project = kwargs["instance"]
created: bool = kwargs["created"]
if created:
user = models.User.objects.get(pk=project.owner.user.id)
assign_perm("read_project", user, project)
Am I using the right approach to filter data relative to each user? How do I combine both the page access limitation and the relative data of each user in a class model view?

How to use foreign key field's attribute for another model field

I have two models in different apps like so:
class Account(models.Model):
"""
Class to store fiat account information of a companies bank account
"""
number = models.CharField(max_length=100)
currency = models.ForeignKey(FiatCurrency, on_delete=models.CASCADE)
owner = models.ForeignKey(Company, on_delete=models.CASCADE)
date_added = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.number
class FiatTransaction(models.Model):
"""
Class to store Transactions made between escrow and operative white-listed fiat accounts
"""
debit_account = models.ForeignKey('company.Account', on_delete=models.CASCADE, related_name='debit_account')
credit_account = models.ForeignKey('company.Account', on_delete=models.CASCADE, related_name='credit_account')
executed_on = models.DateTimeField(auto_now_add=True)
amount = models.FloatField()
currency = debit_account.currency
is_processed = models.BooleanField(default=False)
fee = models.FloatField()
memo = models.CharField(max_length=250)
def __str__(self):
return F"Transferred {self.amount} from {self.debit_account} to {self.credit_account} at {self.executed_on}"
Now the field currency of model FiatTransaction doesn't seem to work the way I intend it to do. It raises
AttributeError: 'ForeignKey' object has no attribute 'currency'
# Source model
class FiatCurrency(models.Model):
"""
A model to store Fiat Currencies offered by Finchin to
include into cash-pools.
"""
ISO_Code = models.CharField(max_length=3)
name = models.CharField(max_length=50)
is_active = models.BooleanField(default=True)
def __str__(self):
return self.name
Why's that and how to make this work?
You can make a #property that will determine the currency of that object with:
class FiatTransaction(models.Model):
debit_account = models.ForeignKey('company.Account', on_delete=models.CASCADE, related_name='debit_account')
credit_account = models.ForeignKey('company.Account', on_delete=models.CASCADE, related_name='credit_account')
executed_on = models.DateTimeField(auto_now_add=True)
amount = models.FloatField()
is_processed = models.BooleanField(default=False)
fee = models.FloatField()
memo = models.CharField(max_length=250)
#property
def currency(self):
return self.debit_account.currency
This can however be inefficient if you have to do this for a lot of FiatTransactions.
In that case it might be better to remove the currency property, and annotate the QuerySet with:
from django.db.models import F
FiatTransaction.objects.annotate(currency=F('debit_account__currency'))
The FiatTransactions that arise from this will have an extra attribute named .currency that will contain the .currency of the .debit_account.
If you need this often, you can make use of a Manager that will automatically annotate when you access FiatTransaction.objects:
from django.db.models import F
class FiatTransactionManager(models.Manager):
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).annotate(
currency=F('debit_account__currency')
)
class FiatTransaction(models.Model):
# …
objects = FiatTransactionManager()

Saving / accessing fields from Class methods (Django)

Appologies for the beginner question and/or stupidity - I'm learning as I go.... I'm trying to pass a user entered url of a PubMed article to access the metadata for that article. I'm using the following code, but I cannot access anything form the save method in he 'Entry' model. For example in my html form I can display {{entry.date_added }} in a form but not {{ entry.title}}. I suspect it's a simple answer but not obvious to me. Thanks for any help.
models.py
from django.db import models
from django.contrib.auth.models import User
import pubmed_lookup
from django.utils.html import strip_tags
class Topic(models.Model):
"""Broad topic to house articles"""
text = models.CharField(max_length=200)
date_added = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
"""Return a string representation of the model"""
return self.text
class Entry(models.Model):
"""Enter and define article from topic"""
topic = models.ForeignKey(Topic, on_delete=models.CASCADE)
pub_med_url = models.URLField(unique=True)
date_added = models.DateTimeField(auto_now_add=True)
def save(self, *args, **kwargs):
query = self.pub_med_url
email = "david.hallsworth#hotmail.com"
lookup = pubmed_lookup.PubMedLookup(query, email)
publication = pubmed_lookup.Publication(lookup)
self.title = strip_tags(publication.title)
self.authors = publication.authors
self.first_author = publication.first_author
self.last_author = publication.last_author
self.journal = publication.journal
self.year = publication.year
self.month = publication.month
self.day = publication.day
self.url = publication.url
self.citation = publication.cite()
self.mini_citation = publication.cite_mini()
self.abstract = strip_tags(publication.abstract)
super().save(*args, **kwargs)
class Meta:
verbose_name_plural = 'articles'
def __str__(self):
return "{} - {} - {} - {} [{}]".format(self.year,
self.first_author, self.journal, self.title, str(self.pmid), )
In Django ORM, you have to manually specify all fields that need to be saved. Simply saving it as self.foo = bar in the save method is stored in the Entry instance object (=in memory), but not in the database. That is, there is no persistence. Specify all the fields that need to be saved in the model and run python manage.py makemigrations,python manage.py migrate. Assigning fields to the model is actually the task of designing the relational database.
class Entry(models.Model):
"""Enter and define article from topic"""
topic = models.ForeignKey(Topic, on_delete=models.CASCADE)
pub_med_url = models.URLField(unique=True)
date_added = models.DateTimeField(auto_now_add=True)
title = models.CharField(...)
authors = models.CharField(...)
...
def assign_some_data_from_pubmed(self):
email = "david.hallsworth#hotmail.com"
lookup = pubmed_lookup.PubMedLookup(query, email)
publication = pubmed_lookup.Publication(lookup)
self.title = strip_tags(publication.title)
self.authors = publication.authors
self.first_author = publication.first_author
self.last_author = publication.last_author
self.journal = publication.journal
self.year = publication.year
self.month = publication.month
self.day = publication.day
self.url = publication.url
self.citation = publication.cite()
self.mini_citation = publication.cite_mini()
self.abstract = strip_tags(publication.abstract)
Usage:
entry = Entry(...)
entry.assign_some_data_from_pubmed()
entry.save()

How to fetch certain field value of related models object and save it to another?

My models Book and ReadList related and I can use them on Django Admin but I want to fetch bookPageCount field of selected object and save it to pageCount field of new object when save.
from django.db import models
from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver
# Create your models here.
class Book(models.Model):
bookname = models.CharField(max_length=200, verbose_name='Kitap Adı')
bookAuthor = models.CharField(max_length=100, verbose_name='Yazar Adı')
bookPublisher = models.CharField(max_length=100, verbose_name='Yayın Evi')
bookPageCount = models.PositiveIntegerField(verbose_name='Sayfa Sayısı')
bookAddDate = models.DateField(verbose_name='Kitaplığa Eklenme Tarihi')
class Meta:
verbose_name = 'Kitap'
verbose_name_plural = 'Kitaplar'
def __str__(self):
return self.bookname
class ReadList(models.Model):
bookName = models.ForeignKey('kitaplik.Book', related_name='book', on_delete=models.CASCADE, verbose_name='Kitap Adı')
readerName = models.ForeignKey('ogrenciler.Students', on_delete=models.CASCADE, related_name='readerName', verbose_name='Okuyan Kişi')
dateOfRead = models.DateField(verbose_name='Okuma Tarihi')
pageCount = models.PositiveIntegerField(verbose_name='Sayfa Sayısı',blank=True, null=True)
class Meta:
verbose_name = 'Okuma Günlüğü'
#receiver(post_save, sender=ReadList)
def get_pageCount(sender, instance, **kwargs):
instance.pageCount = instance.Book.bookPageCount
instance.save
EDIT: Changes been made after Higor's reminder but I still have problem as the same. The error I get is 'ReadList' object has no attribute 'Book'
I think you've mistaken some names.
#receiver(post_save, sender=ReadList)
def get_pageCount(sender, instance, **kwargs):
instance.pageCount = instance.book.bookpageCount
instance.save()
bookName is the field name
#receiver(post_save, sender=ReadList)
def get_pageCount(sender, instance, **kwargs):
instance.pageCount = instance.bookName.bookPageCount
instance.save()

How to form data for select?

There is a model Provider. It has the field role, which has two options: Individual and Organization. There are summary field and organization field.
It is necessary that in the form (in the template), in the select, the data is displayed in the following way: if the record is associated with Individual, then summqry + user, and if with the Organization, then the Organization title.
models.py
ROLE_INDIVIDUAL = 'individual'
ROLE_ORGANIZATION = 'organization'
ROLE_CHOICES = (
(ROLE_INDIVIDUAL, _('Individual')),
(ROLE_ORGANIZATION, _('Organization'))
)
class Provider(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
role = models.CharField(max_length=255, choices=ROLE_CHOICES, default=ROLE_INDIVIDUAL)
summary = models.CharField(max_length=255, default='')
organization = models.ForeignKey(Organization, on_delete=models.CASCADE)
forms.py
class ProductCreateForm(forms.ModelForm):
class Meta:
model = Product
fields = (..., 'on_behalf', ...)
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('initial').get('request')
super(ProductCreateForm, self).__init__(*args, **kwargs)
self.fields['on_behalf'] = ModelChoiceField(queryset=Provider.objects.filter(user=user.id))
You should define the __str__ method on Provider to return the string you want.
class Provider(models.Model):
...
def __str__(self):
if self.role == ROLE_INDIVIDUAL:
return '{} + {}'.format(self.summary, self.user)
else:
return self.organization.title