According to the documentation, to optimize the db access :
If you only need a foreign key value, use the foreign key value that
is already on the object you’ve got, rather than getting the whole
related object and taking its primary key. i.e. do:
entry.blog_id
No problem to use with a ForeignKey and it works as intended.
But if I want to do the same with OneToOneField, it is not working :
Class CustomUser(Model):
...
class Profile(Model):
user = models.OneToOneField(
CustomUser,
on_delete=models.CASCADE,
)
Then, if I try to use the same tip as described in the documentation, in a view for example :
request.user.profile_id
I got the followig error :
AttributeError: 'CustomUser' object has no attribute 'profile_id'
Of course, it works with request.user.profile.uid but it is not the point here because there is an additional query to the DB.
Is it intended ? Is it a con to using OneToOneField ?
Since you're doing it in reverse (it's the model Profile that has the field user_id, and not CustomUser that has the field profile_id) I think you can't use this optimization. You'd have to move the OneToOneField to CustomerUser model. You can still access the object of course, but you're going to hit the database once more.
Edit
If possible for your project, this should work:
class Profile(Model):
...
class CustomUser(Model):
profile = models.OneToOneField(
Profile,
on_delete=models.CASCADE,
)
request.user.profile_id
Related
I have a django 1.6 app with the following (trimmed for clarity)
classes defined. User is the standard django.contrib.auth User class.
class Event(models.Model):
user = models.ForeignKey(User, related_name='events')
name = models.CharField(max_length=64)
class Profile(models.Model):
user = models.ForeignKey(User, related_name='aprofile')
class MemberProfile(Profile):
pass
Here are my admin classes:
class ProfileAdmin(ModelAdmin):
model = Profile
fields = ('user', )
class MemberProfileAdmin(ModelAdmin):
model = MemberProfile
fields = ('user', )
readonly_fields = ('user', )
What I'd like to do is display a read-only list of all events for a given member, or at least profile. Of course joining across the User foreign key seems like the way to go, but I am drawing a blank as to how to accomplish this. Here's a summary of attempts so far.
Define an inline admin on the Event class directly referencing the user field, and add it to the ProfileAdmin:
class EventInlineAdmin(TabularInline):
model = Event
fk_name = 'user' # Fails - fk_name 'user' is not a ForeignKey to <class 'solo365.solo_profile.models.profile.Profile'>
...well, no, it sure isn't. But our User has an 'aprofile' field, so...
class EventInlineAdmin(TabularInline):
model = Event
fk_name = 'user__aprofile' # Fails - EventInlineAdmin.fk_name' refers to field 'user__aprofile' that is missing from model 'admin_fk_test.Event'.
Ok, those fields look like they should sync up, but perhaps we need to be a little more aggressive:
class EventInlineAdmin(TabularInline):
model = Event
fk_name = 'user__aprofile__pk' # Fails - 'EventInlineAdmin.fk_name' refers to field 'user__aprofile__pk' that is missing from model 'admin_fk_test.Event'.
I've also tried messing with formfield_for_foreignkey() and friends in both the inline and the regular model admins, but without that fk_name having a valid value, those methods never get called.
I then considered trying to access the events field directly from a Profile's user:
class ProfileAdmin(ModelAdmin):
model = Profile
fields = ('user', 'user__events') # Fails - Unknown field(s) (user__events) specified for Profile. Check fields/fieldsets/exclude attributes of class ProfileAdmin.
What about with a custom formfield_for_foreignkey() method? Sadly that never gets called for anything other than the 'user' field. I've also considered a custom get_formsets() method, but frankly I'm not sure how I could use that without a working EventInlineAdmin.
I could of course define a custom field that simply concatenates all of the events and returns that as a string, but ideally I would prefer something like a fully-featured inline (even read-only) than just a chunk o' text. IOW such a custom field would have a method that (ideally) would return an inline form without requiring any sort of custom template, setting of allow_tags, etc.
Am I doomed to have to create a completely custom Form for the Profile admin class? Or is there a simple way to accomplish what I'm trying to do, that I'm just missing?
Update:
Bonus points if a provided solution works for the MemberProfileAdmin class, not just the ProfileAdmin class.
The relation between User and Profile should be a 1:1 relation which would allow the referencing via user__aprofile. Otherwise, the reverse relation of a foreing key is a queryset because one foreign key can be assigned to multiple instances. This is might be the reason why your code failed.
Change it to:
class Profile(models.Model):
user = models.OneToOneKey(User, related_name='aprofile')
This is a bit like using ForeignKey(unique=True).
To know the attributes, it might help to call dir(model_instance) on the model instance in question, or try around in the Django shell (./manage.py shell).
Also, I've experienced that it might be more confusing to assign a custom related_name like in your case where you would expect one profile by looking at the related name but you would actually get back a queryset.
The generated name in that case would be profile_set, and you would have to call profile_set.all() or profile_set.values() to get some actual profiles.
Is there a way to not have to pass in a model instance for a foreign key when create a new model? Let's say I have the following models:
class Foo(models.Model):
description = models.CharField(max_length=100)
class Meta:
db_table = u'foo'
class Bar(models.Model):
info = models.CharField(max_length=100)
foo = models.ForeignKey('Foo')
class Meta:
db_table = u'bar'
The later a post request comes in to a view - I know the the id of a foo record and just want to insert a record into the bar table.
if I do:
new_bar = Bar(info="something important", foo=foo_id)
new_bar.save()
I get a ValueError saying "Cannot assign "546456487466L": "Bar.foo" just be a "Foo" instance.
So, I get it... it wants me to have an actual instance of the Foo model. I understand that I can just do a get on Foo and then pass it in. But, there seems like there must be a way to override this functionality. I have done some googling and reading the docs, and raw_id_fields in admin seems to be the basic idea. (which is to say, allow a raw id here). But, don't see this option on the ForeignKey field.
It seems very inefficient to have to make a round trip to the database to get an object to get the id (which I already have). I understand that doing the round trip validates that the id exists in the database. But, hey... that's why I'm using a RDBMS and have foreign keys in the first place.
Thanks
new_bar = Bar(info="something important", foo_id=12345)
new_bar.save()
You can also get foreign key values directly. Some kind of optimization.
Consider the model schema:
class A(models.Model):
id = models.IntegerField(...)
...
class B(models.Model):
parent = models.OneToOneField(A, primary_key=True)
And further assume that there are more rows of A than B (e.g. not all As have details). How would I generate a query that gives me only As which have associated Bs?
I've tried A.objects.filter(b__isnull=False) which doesn't seem to work, it still returns all rows in A.
I just tried this, and it works for me:
A.objects.exclude(b=None)
or, a somewhat hackier version that relies on the (usually) integer non negative nature of primary keys
A.objects.filter(b__id__gte=0)
Now, I have a related_name, so if those don't work for you, try adding related_name.
class Profile(models.Model):
user = models.OneToOneField(User, related_name='profile')
If it's OneToOne, there's no manager, just the object itself. So it should be A.B, I think.
What you have should work. Just verified it myself on a OneToOneField reverse relation in my own app. However, I've never tried doing that on a OneToField that's also the primary key. It's possible that that messes with the query for some reason. It's a little extra work just to test the theory, but you may want to try using a standard AutoField (or let Django create it automatically by removing primary_key) as the primary key, and see if your query works then.
Since all B's must have A (and the reverse is not true):
B.objects.filter(parent__isnull=False)
A one-to-one relationship. Conceptually, this is similar to a
ForeignKey with unique=True, but the "reverse" side of the relation
will directly return a single object.
Is there a way to tell django not to follow a foreign key relationship when you instantiate a model instance? Something to put on the model itself? Something to pass to a queryset? I'd like to have a queryset that only returns instances with the foreign key id -- I don't want the instances to go off making queries to find its relatives. Something like the opposite of select_related?
The default behaviour of Django is to wait until a foreign key relationship is accessed before performing a database queries to populate the related model instance.
To side-step the automatic querying for related instances, rather than accessing the ForeignKey field attribute directly, access attribute_id, e.g.
class Person(models.Model):
name = models.CharField(max_length=200)
user = models.ForeignKey('auth.User')
# access the user id via user_id
person = Person.objects.all()[0]
print person.user_id
Try defer
It appears Django hides fields that are flagged Primary Key from being displayed/edited in the Django admin interface.
Let's say I'd like to input data in which I may or may not want to specify a primary key. How would I go about displaying primary keys in the admin interface, and how could I make specifying it optional?
I also wanted to simply show the 'id' (primary key) within the Django admin, but not necessarily edit it. I just added it to the readonly_fields list, and it showed up fine. IE:
class StudentEnrollmentInline(admin.TabularInline):
model = Enrollment
readonly_fields=('id',)
whereas if I tried to add it to the 'fields' list, Django got upset with me, saying that field didn't exist...
If you explicitly specify the primary key field in your models (with primary_key=True), you should be able to edit it in the admin.
For Django models created via ./manage.py syncdb the following primary key field is added automatically:
id = models.AutoField(primary_key=True)
if you change (or add) that to your model explicitly as an IntegerField primary key, you'll be able to edit it directly using the admin:
id = models.IntegerField(primary_key=True)
But as others pointed out, this is a potential minefield...
To show the primary key, which by default will have a column name of "id" in the database - use "pk"
def __str__(self):
return '{} - {} ({})'.format(self.pk, self.name, self.pcode)
It doesn't make sense to have an optional primary key. Either the PK is an autoincrement, in which case there's no need to edit it, or it's manually specified, in which case it is always required.
Why do you need this?
In django documentation, there is a short sentence about that, which is not clear:
If neither fields nor fieldsets options are present, Django will default to displaying each field that isn't an AutoField and has editable=True, in a single fieldset, in the same order as the fields are defined in the model.
Reason is, django do not allow you to edit an AutoField by any means (and that is the right thing since it is an auto increment value and should not be edited). #mnelson4's answer is a good approach to display it.
The answer with the highest votes didn't work for me. I needed a getter.
class StudentEnrollmentInline(admin.TabularInline):
model = Enrollment
readonly_fields=('student_enrollment_id',)
def student_enrollment_id(self, obj):
return obj.id
Using django 1.11