Stop affecting other objects of Django Many to Many model - python

I'm trying to replicate Blood Group as Model as defined in this picture.
.
In my models.py file I had my code to replicate the blood groups like this
class BloodGroup(models.Model):
name = models.CharField(
max_length=3
)
gives = models.ManyToManyField("self")
receives = models.ManyToManyField("self")
def __str__(self):
return self.name
And in my admin.py file I had registered the model as follows
class BloodGroupAdmin(admin.ModelAdmin):
model = BloodGroup
list_display = ['name', 'get_gives', 'get_receives']
def get_gives(self, obj):
return ", ".join([item.name for item in obj.gives.all()])
def get_receives(self, obj):
return ", ".join([item.name for item in obj.receives.all()])
admin.site.register(BloodGroup, BloodGroupAdmin)
Initially I created plain BloodGroup objects without their gives and receives attribute by providing just their names alone. Thus I create an object for all 8 types. Then as I added relationships to each object I found that adding gives or receives for one object affects other objects gives and receives too, making it impossible to replicate the structure in image.
How do I define relationships, without affecting other objects?
In my admin site, I see field names as "get_gives" and "get_receives". How would i make the admin page show field names as "gives" and "receives" but still displaying objects as strings like the image below?

For first question, probably it is better to have only one relation gives. receives can be found from the reverse query. Like this:
class BloodGroup(models.Model):
name = models.CharField(
max_length=3
)
gives = models.ManyToManyField("self", related_name="receives", symmetrical=False)
Then you only need to add objects to gives. receives will be generated automatically.
For second question, add short_description attribute to function(reference to docs). Like this:
get_gives.short_description = 'Gives'
get_receives.short_description = 'Receives'

Related

Django : Migration of polymorphic models back to a single base class

Let's suppose I have a polymorphic model and I want to get rid of it.
class AnswerBase(models.Model):
question = models.ForeignKey(Question, related_name="answers")
response = models.ForeignKey(Response, related_name="answers")
class AnswerText(AnswerBase):
body = models.TextField(blank=True, null=True)
class AnswerInteger(AnswerBase):
body = models.IntegerField(blank=True, null=True)
When I want to get all the answers I can never access "body" or I need to try to get the instance of a sub-class by trial and error.
# Query set of answerBase, no access to body
AnswerBase.objects.all()
question = Question.objects.get(pk=1)
# Query set of answerBase, no access to body (even with django-polymorphic)
question.answers.all()
I don't want to use django-polymorphic because of performances, because it does not seem to work for foreignKey relation, and because I don't want my model to be too complicated. So I want this polymorphic architecture to become this simplified one :
class Answer(models.Model):
question = models.ForeignKey(Question, related_name="answers")
response = models.ForeignKey(Response, related_name="answers")
body = models.TextField(blank=True, null=True)
The migrations cannot be created automatically, it would delete all older answers in the database. I've read the Schema Editor documentation but it does not seem there is a buildin to migrate a model to something that already exists. So I want to create my own operation to save the AnswerText and AnswerInteger as an Answer then delete AnswerText and AnswerInteger. I'm hoping I won't have to write SQL directly, but maybe that's the only solution ? My migration file looks like this. I created an Operation called MigrateAnswer :
from myapp.migrations import MigrateAnswer
class Migration(migrations.Migration):
operations = [
migrations.RenameModel("AnswerBase", "Answer"),
migrations.AddField(
model_name='answer',
name='body',
field=models.TextField(blank=True, null=True),
),
MigrateAnswer("AnswerInteger"),
MigrateAnswer("AnswerText"),
migrations.DeleteModel(name='AnswerInteger',),
migrations.DeleteModel(name='AnswerText',),
]
So what I want to do in MigrateAnswer is to migrate the value for an old model (AnswerInteger and AnswerText) to the base class (now named Answer, previousely AnswerBase). Here's my operation class :
from django.db.migrations.operations.base import Operation
class MigrateAnswer(Operation):
reversible = False
def __init__(self, model_name):
self.old_name = model_name
def database_forwards(self, app_label, schema_editor, from_state,
to_state):
new_model = to_state.apps.get_model(app_label, "Answer")
old_model = from_state.apps.get_model(app_label, self.old_name)
for field in old_model._meta.local_fields:
# loop on "question", "reponse" and "body"
# schema_editor.alter_field() Alter a field on a single model
# schema_editor.add_field() + remove_field() Does not permit
# to migrate the value from the old field to the new one
pass
So my question is : Is it possible to do this wihout using "execute" (ie : without writing SQL). If so what should I do in the for loop of my Operation ?
Thanks in advance !
There is no need to write an Operations class; data migrations can be done simply with a RunPython call, as the docs show.
Within that function you can use perfectly normal model instance methods; since you know the fields you want to move the data for, there is no need to get them via meta lookups.
However you will need to temporarily call the new body field a different name, so it doesn't conflict with the old fields on the subclasses; you can rename it back at the end and delete the child classes because the value will be in the base class.
def migrate_answers(apps, schema_editor):
classes = []
classes_str = ['AnswerText', 'AnswerInteger']
for class_name in classes_str:
classes.append(apps.get_model('survey', class_name))
for class_ in classes:
for answer in class_.objects.all():
answer.new_body = answer.body
answer.save()
operations = [
migrations.AddField(
model_name='answerbase',
name='new_body',
field=models.TextField(blank=True, null=True),
),
migrations.RunPython(migrate_answers),
migrations.DeleteModel(name='AnswerInteger',),
migrations.DeleteModel(name='AnswerText',),
migrations.RenameField('AnswerBase', 'new_body', 'body'),
migrations.RenameModel("AnswerBase", "Answer"),
]
You could create an empty migration for the app you want to do these modifications and use the migrations.RunPython Class to execute custom python functions.
Inside these functions you can have access to your models
The Django ORM that you can do data manipulation.
Pure python, no raw SQL.

'.....' object is not iterable in django

I have 3 models: User, Choice, Card. Each user will look at the same set of 10 cards and decides each one is important or not.
Here are how I define the classes and their relationship
In models.py:
class Choice(models.Model):
user = models.ForeignKey(User)
card = models.ManyToManyField(Card)
is_important = models.NullBooleanField()
class Card(models.Model):
card_number = models.IntegerField(primary_key=True)
content = models.TextField(null=False)
In views.py
(I try to save the choice for the card from the user. )
def listings(request):
user = request.user
choice = Choice.objects.create(user=user, is_important = True)
choice.card= Card.objects.get(1)
However, I got this error
'Card' object is not iterable
Could you please show me where the error is?
Many thanks!
You can add object against many to many field like this
card = Card.objects.create(card_number=any_number, content='abc')
choice.card.add(card)
First, it looks like you forgot pk= in your first .get() argument: Card.objects.get(pk=1)
Second, Choice.cards is a ManyToManyField that expects a list of items and not one in particular. You should set it through:
choice.card.set(Card.objects.filter(pk=1))
Please note that direct assignment with = will be deprecated from Django 1.10 and deleted in Django 2.0
.filter() will return a QuerySet (which is iterable). I think you wanted a ForeignKey instead of a M2M field, in which case your code would work (with the additional pk=).
In your function:
def listings(request):
user = request.user
choice = Choice.objects.create(user=user, is_important = True)
choice.card= Card.objects.get(1)
The following line is trying to fetch the Card object. However, we need to specify which card to be fetched.
If using an id, query it as:
choice.card= Card.objects.get(pk=1)
or else using list of ids:
choice.card = Card.objects.filter(pk__in=[12,22])
If using card_number field:
choice.card= Card.objects.get(card_number=1)
or else using list of card_numbers:
choice.card = Card.objects.filter(card_number__in=[12,22])

how to modify related object's data whilie creating a new object in django

I am new to django and I'm trying to do something pretty simple.
my models.py is as below:
from django.db import models
class DiskDrive(models.Model):
deviceId = models.CharField(max_length=64, primary_key=True)
freeSpace = models.BigIntegerField()
def __unicode__(self):
return self.deviceId
class StoragePool(models.Model):
poolId = models.CharField(max_length=256, primary_key=True)
size = models.BigIntegerField()
drive = models.ForeignKey(DiskDrive, related_name='pools')
def __unicode__(self):
return self.poolId
I haven't added anything to views.py and urls.py yet.
I'm able to create objects of both the classes.
Whenever I create an object of StoragePool class, I want to reduce the value of 'freeSpace' attribute of the related DiskDrive object by the 'size' of 'StoragePool' object. How should I do this? Please help...
Sounds like the perfect job for a post_save signal:
#receiver(post_save, sender=StoragePool)
def update_drive_space(sender, instance, created, **kwargs):
if created:
instance.drive.freeSpace = F('freeSpace') - instance.size
instance.drive.save(update_fields=['freeSpace'])
See Django signals documentation for more info about how signals work. In a nutshell this method will be called each time you create or update a StoragePool object.
Few notes:
I am using F expression to reference current database size value instead of blindly saving whatever we have on Django side (this will ensure correct value when multiple clients will create new StoragePool objects)
It is likely that you want to adjust freeSpace attribute also when size is updated - not just on StoragePool creation. To make that happen just delete if created check and it will be run on every StoragePool.save()

Accessing Django custom models through Manager returns empty set

From all I've read, it appears this should Just Work, but it doesn't.
I have a custom model:
from django.db import models
from django.contrib.auth.models import *
class Feed(models.Model):
user = models.ForeignKey(User, blank=True, null=True)
link = models.CharField(max_length=200)
startDate = models.CharField(max_length=8)
endDate = models.CharField(max_length=8)
def __unicode__(self):
return str(self.id)
def __init__(self, link, sDate, eDate, user=None):
super(Feed, self).__init__()
self.link = link
self.startDate = sDate
self.endDate = eDate
self.user = user
And I'm also using the User model included in 'django.contrib.auth.models'.
When I create a feed, e.g.
feed = Feed(link, sDate, eDate)
feed.save()
(or a similar one with a user specified) it appears to store it in the database (I get its PK which keeps incrementing), but 'Feed.objects.all()' returns an empty list. Trying to filter by an existing pk also returns an empty list and trying to get() an existing pk gives me the following error
TypeError: __init__() takes at most 5 arguments (6 given)
Looking at how I should be retrieving objects from custom models, it appears that I've done everything I should, but that is clearly not the case...
Whoa.
Why are you overriding your model's __init__? There are very few good reasons to do this, and if you do, you must absolutely keep the interface the same- because that __init__ is called every time django pulls one of your models from the db (which is why you get the error when you call .get())
What are you hoping to accomplish with your __init__?
You should probably just delete your __init__ and then you can create Feed objects the normal, django way:
feed = Feed(link=link, startDate=sDate, endDate=eDate)
That line will create the correct feed object you want.
Did you try named arguments, e.g.
feed = Feed(link=link, startDate=sDate, endDate=eDate)
How did you use get()? It should also be used with named arguments, e.g.:
Feed.objects.get(pk=6)

How can I add class attributes to Models in Django?

I'm using django to build an internal webapp where devices and analysis reports on those devices are managed.
Currently an abstract Analysis is defined like this:
class Analysis(models.Model):
project = models.ForeignKey(Project)
dut = models.ForeignKey(Dut) # Device Under Test
date = models.DateTimeField()
raw_data = models.FileField(upload_to="analysis")
public = models.BooleanField()
#property
def analysis_type(self):
s = str(self.__class__)
class_name = s.split('.')[-1][:-2] # Get latest name in dotted class name as remove '> at end
return AnalysisType.objects.get(name=class_name)
class Meta:
abstract = True
There are then a number of different analysis types that can be done on a device, with different resulting data.
class ColorAnalysis(Analysis):
value1 = models.FloatField()
value2 = models.FloatField()
...
class DurabilityAnalysis(Analysis):
value1 = models.FloatField()
value2 = models.FloatField()
...
...
Each such analysis is created from an Excel sheet posted by the operator. There exists an Excel template the operator fills in for each analysis type.
(The issue here is not if data input should be done in a web form, there are lots of reasons to choose the Excel path)
On a page on the website all analysis types should be listed along with a link to the corresponding Excel sheet template used to report that analysis.
Currently I have defined something like
class AnalysisType(models.Model):
name = models.CharField(max_length=256 )
description = models.CharField(max_length=1024,blank=True )
template = models.FileField(upload_to="analysis_templates")
but when I though about how I would link this data to the different analysis result model classes I though that what I want to do is to add this data as class attributes to each analysis type.
The problem is that the class attributes are already used by the django magic to define the data of each instance.
How do I add "class attributes" to django models? Any other ideas on how to solve this?
EDIT:
Now added the analysis_type property by looking up the class name. This requires no manual adding of a variable to each sub-class. Works fine, but still requires manual adding of an entry of AnalysisType corresponding to each sub-class. It would be nice if this could be handled by the class system as well. Any ideas?
How about a property or method that returns an AnalysisType dependent on an attribute in the particular Analysis subclass?
class Analysis(models.Model):
...
#property
def analysis_type(self):
return AnalysisType.objects.get(name=self.analysis_type_name)
class ColorAnalysis(Analysis):
analysis_type_name = 'color'
class DurabilityAnalysis(Analysis):
analysis_type_name = 'durability'

Categories

Resources