Django: Linking two tables - python

first of all, I'm aware that this question might've been answered already, but there are two reasons why I'm opening another question: One, obviously, is I'm struggling with the Django syntax. Secondly, and perhaps more importantly, I'm not quite sure whether my database setup makes even sense at this point. So, please bear with me.
I work in a hospital and one of my daily stuggles is that, oftentimes, one single drug can have a lot of different names. So, I thought that'd be a good task to practice some Django with.
Basically I want two databases: One that simply links the drugs "nick name" to it's actual name. And another one which links the actual name to some additional information, something along the lines of a wiki page.
What I've come up with so far:
(django)django#ip:~/medwiki$ cat medsearch/models.py
from django.db import models
# Create your models here.
class medsearch(models.Model):
proprietary_name = models.CharField(max_length = 100, unique = True)
non_proprietary_name = models.CharField(max_length = 100, unique = True)
def __str__(self):
return self.non_proprietary_name
class medwiki(models.Model):
proprietary_name = models.ForeignKey('medisearch', on_delete=models.CASCADE)
cetegory = models.CharField(max_length = 255)
#wiki = models.TextField() etc.
def __str__(self):
return self.proprietary_name
(django)django#ip-:~/medwiki$
So, I can add a new "medsearch object" just fine. However, when adding the "Category" at medwiki I get __str__ returned non-string (type medsearch). Presumably, because there's more than one key in medsearch? I thus suspect that "FroeignKey" is not suited for this application and I know that there are other ways to link databases in Django. However, I don't know which one to choose and how to implement it correctly.
Hopefully, some of you have some ideas?
EDIT: Here's what I've come up with so far:
class Proprietary_name(models.Model):
proprietary_name = models.CharField(max_length = 100, unique = True)
def __str__(self):
return self.proprietary_name
class Category(models.Model):
category = models.CharField(max_length = 100, unique = True)
def __str__(self):
return self.category
class Mediwiki(models.Model):
proprietary_name = models.ManyToManyField(Proprietary_name)
non_proprietary_name = models.CharField(max_length = 100, unique = True)
category = models.ManyToManyField(Category)
wiki_page = models.TextField()
def __str__(self):
return self.non_proprietary_name
Now I can attribute different categorys and different proprietary_names to one drug. Which works great so far.
So does looking up the non-proprietary_name when I know the proprietary "nick name".
>>> Mediwiki.objects.get(proprietary_name__proprietary_name="Aspirin")
<Mediwiki: acetylsalicylic acid>
>>>
However, I'd also like to display all the proprietary_names, when I know the non_proprietary_name. Do I have to further change the database design, or am I just missing some other thing here?

This would work:
return self.proprietary_name.proprietary_name
But that doesn't really make sense !
The main issue is that you've called the foreign key to medsearch, proprietary_name.
The second issue is just a convention one. In Python (and many programming languages), classes must start with an uppercase letter.
The following would be better:
class MedSearch(models.Model):
proprietary_name = models.CharField(max_length=100, unique=True)
non_proprietary_name = models.CharField(max_length=100, unique=True)
def __str__(self):
return self.non_proprietary_name
class MedWiki(models.Model):
med_search = models.ForeignKey('MedSearch', on_delete=models.CASCADE, related_name='wikis')
cetegory = models.CharField(max_length = 255)
#wiki = models.TextField() etc.
def __str__(self):
return self.med_serach.proprietary_name

As you note, the proprietary_name field on medwiki is a ForeignKey. You can't return that value directly from the __str__ method because that needs to return a string. You need to convert that value into a string before returning it: either use the default string representation of the medsearch instance:
return str(self.proprietary_name)
or choose a specific string field to return:
return self.proprietary_name.proprietary_name

Related

django enumerate a model's integer field when another model gets created

from django.db import models
class Game(models.Model):
description = models.TextField(max_length=8192)
class GamePreview(models.Model):
game = models.OneToOneField(Game, on_delete=models.CASCADE)
comments = models.IntegerField(default=0) # Want to + 1 this when a comment gets created
class GameComment(models.Model):
game = models.ForeignKey(Game, on_delete=models.CASCADE)
comment = models.CharField(max_length=512)
#classmethod # does not work
def create(cls, game):
comment = cls(game=game)
preview = GamePreview.objects.get(game=comment.game)
preview.comments += 1
return preview
Basically, I have a GamePreview model that has a IntgerField that should show the amount of comments, but I cannot figure out how I can do preview.comments += 1 when a GameComment gets created...
Please don't, you can annotate the GamePreview object to determin the number of comments.
You thus can remove the comments field:
class GamePreview(models.Model):
game = models.OneToOneField(Game, on_delete=models.CASCADE)
# no comments
and then in case you need the number of related GameComments, you can work with .annotate(…) [Django-doc]:
from django.db.models import Count
GamePreview.objects.annotate(
comments=Count('game__gamecomment')
)
GamePreview objects that arise from this queryset will have an extra attribute .comments that contains the number of related GameComments.
If you really want to increment the number of comments, you can work with:
class GameComment(models.Model):
game = models.ForeignKey(Game, on_delete=models.CASCADE)
comment = models.CharField(max_length=512)
#classmethod # does not work
def create(cls, game, comment):
comment = cls(game=game, comment=comment)
preview = GamePreview.objects.get(game_id=comment.game_id)
preview.comments = F('comments') + 1
preview.save()
return comment
But this is usually not a good idea: if a comment is removed, or no longer belongs to that game but to another, you will need to write some logic to change this. Often it is quite hard to cover all possible cases.

Django Enumerate my related many to many objects

I have those models:
class Question(models.Model):
description = models.CharField(max_length = 255)
class Quiz(models.Model):
name = models.CharField(max_length = 32)
questions = models.ManyToManyField(Question, related_name = 'questions')
Im interested to have a property where it returns the index value for the related quiz.
So when i do like this in my views:
def view_quiz(request,slug):
quiz = Quiz.objects.get(name = slug)
questions = quiz.questions.all()
return render(request = request, template_name = 'main/quiz.html', context = {'quiz': quiz,'questions': questions})
I would be able also to access the index of the question.
I was thinking to create a property to question model like this:
class Question(models.Model):
description = models.CharField(max_length = 255)
options = models.ManyToManyField(Option, related_name = 'options',default = None)
#property
def question_number(self):
return 'index of the related quiz'
But i could not figure out the code for that property so it would return the index of the related questions.
Any suggestions? Thanks
A simple way of doing this would be to inject each index into the questions returned by the queryset. This should be a model method on the Quiz, since two quizzes could share the same Question objects.
Note: The index of the question is order dependent, and your quiz.questions.all() queryset has unstable ordering right now – the database won't necessarily return the questions in the same order each time. To force stable ordering, I'm going to assume that the Question instances are ordered by name.
class Quiz(models.Model):
...
def ordered_questions(self):
questions = self.questions.order_by("name")
# You can change to zero based indexing using `start=0`
for index, question in enumerate(questions, start=1):
question.index = index # Inject the index into the question
# Use a yield statement here to keep the queryset lazy and efficient.
yield question
Then wherever you need to access each question's index, you can use the quiz.ordered_questions() method.

Function to copy fields from a model to another model in django

I want to merge 2 different models with different but overlapping fields.
I try to write a function that copy the fields with its data from model A to model B.
def create_field():
old = DetailItem.objects.all()
new = CrawlItem.objects.all()
for item in old:
c = CrawlItem.objects.get(title=item.title, description=item.description, link=item.link, cpvcode=item.cpvcode, postalcode=item.postalcode )
c.save()
and i don't know whereis the mistake. I want to have a model that contains the data from the old model and some new fields.
Here is my code for the two models:
class DetailItem(Base):
title = models.CharField(max_length=500)
description = models.CharField(max_length=20000)
link = models.URLField()
cpvcode = models.ManyToManyField('CPVCode',related_name='cpv')
postalcode = models.ForeignKey('PostalCode',on_delete=models.SET_NULL,null=True,blank=True,related_name='postal')
def __str__(self):
return self.title
class CrawlItem(Base):
guid = models.CharField( primary_key=True, max_length=500)
title = models.CharField(max_length=500)
link = models.URLField()
description = models.CharField(max_length=2000)
pubdate = models.DateTimeField()
detail = models.ForeignKey('DetailItem',on_delete=models.SET_NULL,null=True,blank=True,related_name='crawldetail')
def __str__(self):
return str(self.title)
This is what I want to get:
class CrawlItem(Base):
guid = ...
title = ...
link = ...
cpvcodes = ...
postalcode = ...
pubdate = ...
vergabestelle = ...
vergabeart = ...
anmeldefrist = ...
description = ...
Any ideas how to get there are highly appreciated!
It's not entirely clear which objects already exist in your database, and when you consider two objects to be "equal". Assuming a CrawlItem is "equal" to a "DetailItem" when title, description and link are the same, then you can use the update_or_create function like this:
for item in old:
CrawlItem.objects.update_or_create(
# if matching, existing item updated, otherwise new item created
title=item.title, description=item.description, link=item.link,
defaults = {'cpvcode': item.cpvcode, 'postalcode': item.postalcode}
)
Alternatively, if the two models are linked with the fk as shown in your models (and you want to remove it later on), then you don't even need to check for "equal" objects because you already have all the related ones (assuming title, description and link are already equal):
for item in old:
item.crawldetail.all().update(cpvcode=item.cpvcode, postalcode=item.postalcode)
in your for statement you are just trying to select de CrawlItem with the same values as DetailItem using the get queryset method.
if you want to create a CrawlItem you should use the create method (docs here -> https://docs.djangoproject.com/en/2.2/ref/models/querysets/#create)
c = CrawlItem.objects.create(title=item.title, ..., postalcode=item.postalcode)
it will be created when create() is called, so, you don't need to save it afterwards, c is set to the newly created object.
For performance reasons you can use bulk_create() method as follows (docs here -> https://docs.djangoproject.com/en/2.2/ref/models/querysets/#bulk-create)
new_crawlitems = []
for item in old:
new_crawlitems.append(CrawlItem(title=item.title, description=item.description, link=item.link, cpvcode=item.cpvcode, postalcode=item.postalcode)
CrawlItem.objects.bulk_create(new_crawlitems)
Hope this helps and put you on the right direction.
G.

How to return objects by popularity that was calculated with django-hitcount

I have itens in my app that I want to be returned by "popularity". This popularity meaning the number of views the item has.
I'm using django-hitcount to do this. I saw here how I could get the number of hits of each object. But I don't want to load all my Item objects to memory to accomplish what I want because it's an unnecessary overload.
I want to return the N most popular itens to be passed to the view and the access number of each item.
My Item model is as bellow
class Item(models.Model, HitCountMixin):
nome = models.CharField(max_length=255, unique=True)
slug = models.SlugField(max_length=255, null=True)
imagem = models.ImageField(upload_to='itens/item/', null=True, blank=True)
descricao = RichTextUploadingField(null=True, blank=True)
categoria = models.ForeignKey(Categoria)
hit_count_generic = GenericRelation(
HitCount, object_id_field='object_pk',
related_query_name='hit_count_generic_relation')
def __str__(self):
return '{}'.format(self.nome)
def get_absolute_url(self):
from django.urls import reverse
return reverse('itens:detail_item', args=[str(self.slug)])
At first, in my View I was trying to get the most popular itens with this function
def get_most_popular_itens(amount):
return Item.objects.order_by('-hit_count.hits')[:amount]
But it didn't work. I couldn't understand how this contenttype/generic relationship works.
So, I saw how the database tables were and managed to do something functional (see bellow).
But it has one problem. The queryset returned isn't ordered by the number of views and I don't have access to this number.
Even more, it seems to me that my solution is at least bad.
So, I wanted any idea on how I could improve that, maybe taking some advantage from the Generic Relationship?
def get_most_popular_itens(amount):
ct = ContentType.objects.get_for_model(Item)
hit_counts = HitCount.objects.filter(content_type_id=ct.id).order_by('-hits')[:amount]
items = []
for hit in hit_counts:
items.append(hit.object_pk)
return Item.objects.filter(id__in=items)
This should work:
Item.objects.all().order_by('-hit_count_generic__hits')

How to create django database model that "knows" what kind of category it is?

In Django, I have the following models.py
class Product(RandomPrimaryIdModel):
title = models.CharField(max_length=20, blank=True, null=True)
price = models.CharField(max_length=20, blank=True, null=True)
condition = models.CharField(max_length=20, blank=True, null=True)
class Mattress(Product):
length = models.CharField(max_length=50)
size = models.CharField(max_length=5)
class Pillow(Product):
shape= models.CharField(max_length=50)
comfort= models.CharField(max_length=5)
The idea is that there's a "product" model and several "product_type" models. I'm trying to create a database scheme that relates the two. The end goal is so that when I given access to a primary id for an object whose product_type is unknown, I can simply query/filter that object to find out what the product_type is of the object.
I know that sounds a bit confusing, but how would I go about implementing the correct way? The current scheme (the one above) is not the correct solution I believe.
According to the docs on multi-table inheritance you can reference the lowercase name of the model. In your case to find out the "product type" you'd do something like:
product = Product.objects.get(id=12)
try:
mattress = product.mattress
is_mattress = True
except Mattress.DoesNotExist:
is_mattress = False
You could abstract this out to a helper method that would do the tests for you and return the type as a string or enum of some sort.
If you have a reference to an object, can't you use something like:
p = Product.objects.get(id=1)
class_of_p = str(p.__class__)
and then parse the resulting string
"<class 'whatever.models.Pillow'>"
to find what you need? Apologies if I'm missing something.

Categories