Django Generic Relations not working - python

So, after adding or removing a GenericRelation to one of my model classes nothing happens.
I try to makemigrations and it tells me no changes were detected. So there must be something wrong, because it should be hitting the database and try to apply some changes.
I followed Django example and I can't make the relationship work.
class Person(models.Model):
identity = models.CharField(max_length=13, verbose_name="ID")
name = models.CharField(max_length=255, verbose_name="Name")
board = GenericRelation('second_app.BoardMember') #Second Try
def __unicode__(self):
return self.identity
class Meta:
verbose_name = "Person"
verbose_name_plural = "People"
class Student(Person):
class Meta:
proxy = True
class Parent(Person):
class Meta:
proxy = True
class Teacher(Person):
board = GenericRelation('second_app.BoardMember') # first try
class Meta:
proxy = True
On a different app I have the following model.
class BoardMember(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id', for_concrete_model=False)
responsabilities = models.CharField(max_length=255)
At first I tried setting the Generic Relation on a proxy model. Nothing happened, then I tried setting it on the main Person class. Nothing. This is what I did to test the relation on the shell.
>>>from first_app.models import Teacher
>>>from second_app.models import BoardMember
>>>teacher = Teacher(identity='123456', name='Fermin Arellano')
>>>teacher.save()
>>>bm = Boardmember(content_object=teacher,responsabilities='Check stuff')
>>>bm.save()
>>>teacher.board.all()
[]
Following this example: https://docs.djangoproject.com/en/1.8/ref/contrib/contenttypes/#reverse-generic-relations
The expected result should be: [<Teacher: 123456>]
Am I doing something wrong? There are no errors showing anywhere. Data is saved properly, both the Teacher and BoardMemer objects were created successfully in my database.

I just removed for_concrete_model=False from the GenericForeignKey declaration. Although on Django´s documentation it clearly states that it has to be setted to false in order to use ProxyModels.
Everythings is working fine now.
EDIT.
I just realized that the problem persists. After further investigation I noticed that in order to get the generic relation to work I need to save the content_type_id of the Person model, and not the proxy one. That is why deleting the for_concrete_model parameter helped, because this way I told Django to use the parents content type, and there it worked fine. Funny thing is that if I do the following the relations still works eventhough I have the content_type_id of Person.
Teacher.objects.filter(board__isnull=False)
This returns all the teachers who are in the board.
This is really confusing, if you can shed some light on this mess I'll be very thankful.

Related

In a Django admin, add an inline of a generic relation

Here are my simplified models :
from django.contrib.contenttypes.fields import (
GenericForeignKey, GenericRelation)
from django.db import models
from django.utils.translation import ugettext_lazy as _
class Thing(models.Model):
'''
Our 'Thing' class
with a link (generic relationship) to an abstract config
'''
name = models.CharField(
max_length=128, blank=True,
verbose_name=_(u'Name of my thing'))
# Link to our configs
config_content_type = models.ForeignKey(
ContentType,
null=True,
blank=True)
config_object_id = models.PositiveIntegerField(
null=True,
blank=True)
config_object = GenericForeignKey(
'config_content_type',
'config_object_id')
class Config(models.Model):
'''
Base class for custom Configs
'''
class Meta:
abstract = True
name = models.CharField(
max_length=128, blank=True,
verbose_name=_(u'Config Name'))
thing = GenericRelation(
Thing,
related_query_name='config')
class FirstConfig(Config):
pass
class SecondConfig(Config):
pass
And Here's the admin:
from django.contrib import admin
from .models import FirstConfig, SecondConfig, Thing
class FirstConfigInline(admin.StackedInline):
model = FirstConfig
class SecondConfigInline(admin.StackedInline):
model = SecondConfig
class ThingAdmin(admin.ModelAdmin):
model = Thing
def get_inline_instances(self, request, obj=None):
'''
Returns our Thing Config inline
'''
if obj is not None:
m_name = obj.config_object._meta.model_name
if m_name == "firstconfig":
return [FirstConfigInline(self.model, self.admin_site), ]
elif m_name == "secondconfig":
return [SecondConfigInline(self.model, self.admin_site), ]
return []
admin.site.register(Thing, ThingAdmin)
So far, I've a Thing object with a FirstConfig object linked together.
The code is simplified: in an unrelevant part I manage to create my abstract Config at a Thing creation and set the right content_type / object_id.
Now I'd like to see this FirstConfig instance as an inline (FirstConfigInline) in my ThingAdmin.
I tried with the django.contrib.contenttypes.admin.GenericStackedInline, though it does not work with my current models setup.
I tried to play around with the fk_name parameter of my FirstConfigInline.
Also, as you can see, I tried to play around with the 'thing' GenericRelation attribute on my Config Model, without success..
Any idea on how to proceed to correctly setup the admin?
According to the Django Docs you have to define the ct_fk_field and the ct_field if they were changed from the default values. So it may be enough to set ct_field to config_content_type.
Hope it works!
edit: Those values have to be declared in the Inline:
class SecondConfigInline(admin.StackedInline):
model = SecondConfig
ct_fk_field = "config_object_id"
ct_field = "config_content_type"
edit2:
I just realized an error in my assumption. Usually you should declare the Foreignkey on the Inline-model. Depending on the rest of your code you could just remove the generic Foreignkey on Thing+the genericRelation on Config and declare a normal Foreignkey on the Config-Basemodel.
This question is old, but I'll give it a try anyway.
I think the solution depends on what kind of relation you intend to create between Thing and your Config subclasses.
many-to-one/one-to-many
The way it is currently set up, it looks like a many-to-one relation: each Thing points to a single Config subclass, and many Things can point to the same Config subclass. Due to the generic relation, each Thing can point to a different model (not necessarily a Config subclass, unless you do some extra work).
In this case I guess it would make more sense to put the inline on the admin for the Config. That is, create a GenericStackedInline for Thing (which has the GenericForeignkey), and add the inline to a ConfigAdmin, which you can then use for all Config subclasses. Also see the example below. The generic inline will then automatically set the correct content_type and object_id.
many-to-many
On the other hand, if you are looking for a many-to-many relation between Thing and each Config subclass, then I would move the GenericForeignkey into a separate many-to-many table (lets call it ThingConfigRelation).
A bit of code says more than a thousand words, so let's split up your Thing class as follows:
class Thing(models.Model):
name = models.CharField(max_length=128)
class ThingConfigRelation(models.Model):
thing = models.ForeignKey(to=Thing, on_delete=models.CASCADE)
content_type = models.ForeignKey(ContentType, null=True, blank=True,
on_delete=models.CASCADE)
object_id = models.PositiveIntegerField(null=True, blank=True)
config_object = GenericForeignKey(ct_field='content_type',
fk_field='object_id')
Now it does make sense to add an inline to the ThingAdmin. The following is a bare-bones example of an admin that works for both sides of the relation:
from django.contrib import admin
from django.contrib.contenttypes.admin import GenericStackedInline
from .models import Thing, FirstConfig, SecondConfig, ThingConfigRelation
class ConventionalTCRInline(admin.StackedInline):
model = ThingConfigRelation
extra = 0
class GenericTCRInline(GenericStackedInline):
model = ThingConfigRelation
extra = 0
class ThingAdmin(admin.ModelAdmin):
inlines = [ConventionalTCRInline]
class ConfigAdmin(admin.ModelAdmin):
inlines = [GenericTCRInline]
admin.site.register(Thing, ThingAdmin)
admin.site.register(FirstConfig, ConfigAdmin)
admin.site.register(SecondConfig, ConfigAdmin)
Note that we use the conventional inline for the ForeignKey-side of the relation (i.e. in ThingAdmin), and we use the generic inline for the GenericForeignKey-side (in ConfigAdmin).
A tricky bit would be filtering the content_type and object_id fields on the ThingAdmin.
... something completely different:
Another option might be to get rid of the GenericForeignKey altogether and use some kind of single-table inheritance implementation with plain old ForeignKeys instead, a bit like this.

Can't disable ForeignKey referential integrity check in Django 1.9

I have a model with two entities, Person and Code. Person is referenced by Code twice, a Person can be either the user of the code or the approver.
What I want to achieve is the following:
if the user provides an existing Person.cusman, no further action is needed.
if the user provides an unknown Person.cusman, a helper code looks up other attributes of the Person (from an external database), and creates a new Person entity.
I have implemented a function triggered by pre_save signal, which creates the missing Person on the fly. It works fine as long as I use python manage.py shell to create a Code with nonexistent Person.
However, when I try to add a new Code using the admin form or a CreateView descendant I always get the following validation error on the HTML form:
Select a valid choice. That choice is not one of the available choices.
Obviously there's a validation happening between clicking on the Save button and the Code.save() method, but I can't figure out which is it. Can you help me which method should I override to accept invalid foreign keys until pre_save creates the referenced entity?
models.py
class Person(models.Model):
cusman = models.CharField(
max_length=10,
primary_key=True)
name = models.CharField(max_length=30)
email = models.EmailField()
def __unicode__(self):
return u'{0} ({1})'.format(self.name, self.cusman)
class Code(models.Model):
user = models.ForeignKey(
Person,
on_delete=models.PROTECT,
db_constraint=False)
approver = models.ForeignKey(
Person,
on_delete=models.PROTECT,
related_name='approves',
db_constraint=False)
signals.py
#receiver(pre_save, sender=Code)
def create_referenced_person(sender, instance, **kwargs):
def create_person_if_doesnt_exist(cusman):
try:
Person = Person.objects.get(pk=cusman)
except Person.DoesNotExist:
Person = Person()
cr = CusmanResolver()
Person_details = cr.get_person_details(cusman)
Person.cusman = Person_details['cusman']
Person.name = Person_details['name']
Person.email = Person_details['email']
Person.save()
create_Person_if_doesnt_exist(instance.user_id)
create_Person_if_doesnt_exist(instance.approver_id)
views.py
class CodeAddForm(ModelForm):
class Meta:
model = Code
fields = [
'user',
'approver',
]
widgets = {
'user': TextInput,
'approver': TextInput
}
class CodeAddView(generic.CreateView):
template_name = 'teladm/code_add.html'
form_class = CodeAddForm
You misunderstood one thing: You shouldn't use TextField to populate ForeignKey, because django foreign keys are populated using dropdown/radio button to refer to the id of the object in another model. The error you got means you provided wrong information that doesn't match any id in another model(Person in your case).
What you can do is: not using ModelForm but Form. You might have some extra work to do after you call form.is_valid(), but at least you could code up your logic however you want.

How to get ForeignKey model field

In Django 1.8
class OtherModel(models.Model):
somefield = models.CharField(max_length=20)
class Orderform(models.Model):
sell_item_id = models.CharField(max_length=20)
class Selled(models.Model):
orderform = models.ForeignKey("Orderform")
sell_count = models.IntegerField()
something = OtherModel.objects.get(id=sell_item_id)
I need to use something like OtherModel.objects.get(id=sell_item_id).
How to get sell_item_id in class Selled(models.Model):?
You schema couldn't be presented in SQL.
Option #1:
class Orderform(models.Model):
sell_item_id = models.CharField(max_length=20)
othermodel = models.OneToOneField("OtherModel")
and get it
Selled.objects.get(pk=1).orderform.othermodel
Option #2:
class Selled(models.Model):
orderform = models.ForeignKey("Orderform")
sell_count = models.IntegerField()
def something(self):
return OtherModel.objects.get(id=self.sell_item_id)
and get
Selled.objects.get(pk=1).something()
But I think you should better think about you DB schema.
It looks like you have a couple of questions, for the first, to get the related
Selled.objects.filter(order_form__sell_item_id =id_to_get).select_related('order_form')
Notice the __ (double underscore) before sell_item_id. This is important because it says, selected Selleed by the sell_item_id of the OrderForm. and select_related makes sure that order form is brought back in the results with a single call to the db.
Now, if you want to do that for OtherModel, you will need to create a similar ForeignKey field in the OtherNodel and this will allow you to make the same query as above. Currently, you have no such relation.
class OtherModel(models.Model):
somefield = models.CharField(max_length=20)
orderform = models.ForeignKey("Orderform")
OtherModel.objects.filter(order_form__sell_item_id =id_to_get).select_related('order_form')
Don't forget to run:
python manage.py makemigration
python manage.py migrate
This should solve the issue.

Django: "UserProfileRole.userProfile" must be a "UserProfile" instance

I'm having a strange riddle to solve:
I extended my django-1.4 user-objects with a UserProfile, as described at https://docs.djangoproject.com/en/dev/topics/auth/ and wanted to implement project-specific roles. So my models look like the following:
class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True)
projects = models.ManyToManyField(Project, through='UserProjectRole')
[...]
class UserProjectRole(models.Model):
userProfile = models.ForeignKey(UserProfile)
project = models.ForeignKey(Project)
group = models.ForeignKey(Group)
[...]
I needed to pass a css-class, so I created a model-form for UserProjectRole and implemented the userProfile-Field with a widget:
class ProjectRoleForm(forms.ModelForm):
userProfile = forms.ModelMultipleChoiceField(label='Users',
queryset=UserProfile.objects.all(),
widget=forms.SelectMultiple(attrs={'class': 'select-multiple'}))
class Meta:
model = UserProjectRole
The form is presented correctly, however, it's crashing during save-process with the following error
Cannot assign "[<UserProfile: MyUser>]": "UserProjectRole.userProfile" must be a "UserProfile" instance.
Does anyone have an idea?
My guess is it's because you are using a forms.SelectMultiple widget. Which gives you a list of UserProfile instances ( [<UserProfile: MyUser>] ) and not a single UserProfile instance which is of course required to set on a ForeignKey field (UserProjectRole.userProfile). Thus I suggest to try using a forms.Select widget instead.

Django admin List Display + ForeignKey = Empty Change List

I've got a weird problem in django admin list_display. Whenever I add a foreign key to a list_display the whole change list view goes blank showing only the total no of entries.
models.py:
class Organization(models.Model):
org_id = models.AutoField(primary_key=True)
org_name = models.CharField(max_length=288)
def __unicode__(self):
return self.org_name
class Meta:
db_table = u'organization'
class Server(models.Model):
server_id = models.AutoField(primary_key=True)
server_name = models.CharField(max_length=135,verbose_name="Server Name")
org = models.ForeignKey(Organization,verbose_name="Organization")
def __unicode__(self):
return self.server_name
class Meta:
db_table = u'server'
admin.py:
class ServerAdmin(admin.ModelAdmin):
list_display = ('server_name','org')
admin.site.register(Server,ServerAdmin)
Now I'd expect this code to show me the organization name in the ChangeList View, But instead I get this:
If I remove the org in the list_display of ServerAdmin class, I get this:
I didn't modify the template or override any ModelAdmin methods. I'm using Mysql(5.1.58) as my database that comes with ubuntu 11.10 repository.
I'll be really glad if I could a get a sloution for this problem guys. Thanks in advance.
I second Stefano on the fact that null=True, blank=True is to be added. But, I think you only need to add it to the org_name field of the Organization model. That should make your way through. It has to be done because you have run inspectdb to create models from your legacy DB. And probably the organization table in the DB has an empty string stored. So, adding the above would allow the Admin to have a blank field/column displayed.
Moreover, you can also try using callbacks in situations where you don't want to make changes to your model definition like the above.
Try adding null=True, blank=True to all your model fields.
Usually django admin will silenty fail (thus show no records in the list) if the row does not validate the model constraints.
See: https://stackoverflow.com/a/163968/1104941
Does the following work for you?
admin.py:
class ServerAdmin(admin.ModelAdmin):
list_display = ('server_name','org__org_name')
admin.site.register(Server,ServerAdmin)
I had a similar problem and solved it like this (using your example):
class ServerAdmin(admin.ModelAdmin):
list_display = ('server_name', 'get_org')
def get_org(self, obj):
return obj.org.org_name
get_org.short_description = 'Org'
admin.site.register(Server,ServerAdmin)

Categories

Resources