python django list_display on related inlines [duplicate] - python

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Can “list_display” in a Django ModelAdmin display attributes of ForeignKey fields?
I want to show some information on the admin list view of a model which comes from another, related model.
class Identity(models.Model):
blocked = models.BooleanField()
...
class Person(models.Model):
modelARef = OneToOneField("Identity", primary_key=True)
descr = models.CharField(max_length=255)
name = models.CharField(max_length=255)
The User should be able to add/edit "Person" on the admin page. As there ist no support for reverse inlining I have to show "Identity" on the admin page and then inline "Person".
"Identity" only contains additional information to "Person" which should be visible on the admin page.
Now when I have a admin page for "Identity" how can I show fields from the "Person"-model on the list_display of "Identity"?
regards
EDIT: I can add some functions to "Identity" which query the related "Person" and return the needed value but if I do that there is no possibility to sort that column.

You can use a list_display to add custom columns. I'd also advise updating the get_queryset() to make sure the related objects are only fetched on one query, instead of causing a query per row.
class IdentityAdmin(admin.ModelAdmin):
list_display = ('blocked', 'person_name')
def person_name(self, object):
return object.person.name
person_name.short_description = _("Person name")
def get_queryset(self, request):
# Prefetch related objects
return super(IdentityAdmin, self).get_queryset(request).select_related('person')

Not directly, but you can create a method that prints out what you want, and add the method name to list_display. See docs on list_display

Related

Django: how can I a create categories field/dropdown menu?

This is a very simple question, how can I create dropdown field in Django with only specific categories(something similar to the countries dropdown, but not with countries).
With the choice attribute of a Field if it is for fixed values. Or with a ForeignKey field if the Categories should be created dynamicly.
For the ForeignKey field you would do the following:
from django.db import models
class Category(models.Model):
name = models.Charfield(max_length=255)
# ...
def __str__(self):
return self.name
class Item(models.Model):
category = models.ForeignKey(Category)
# ...
Django's most powerful feature is to offer you direct forms.
It's a broad question but you want to define a model that can be paired with a form that you can put in a template.
Take a look here: https://docs.djangoproject.com/en/1.8/topics/forms/ and here:
Django options field with categories and here Creating a dynamic choice field

Django admin search and edit foreign fields

I've got a two part question regarding Django Admin.
Firstly, I've got a Django model Classified that has a foreign key field from another table Address. On setting data, I've got no issues with any of the fields and all fields get saved correctly.
However, if I want to edit the foreign field in the entry in Classified, it doesn't display the old/existing data in the fields. Instead it shows empty fields in the popup that opens.
How do I get the fields to display the existing data on clicking the + so that I can edit the correct information?
Secondly, I'm sure I've seen search fields in Django Admin. Am I mistaken? Is there a way for me to implement search in the admin panel? I have over 2 million records which need to be updated deleted from time to time. It's very cumbersome to manually go through all the pages in the admin and delete or edit those.
Adding Model Code:
Classified
class Classified(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=256)
contact_person = models.CharField(max_length=300, blank=True)
email = models.CharField(max_length=100, blank=True)
address = models.ForeignKey(Address)
subcategory = models.ForeignKey(Subcategory)
Address
class Address(models.Model):
id = models.AutoField(primary_key=True)
build_add = models.CharField(max_length=255)
street_add = models.CharField(max_length=255)
area = models.CharField(max_length=255)
city = models.ForeignKey(Cities)
The + means just that - add a new instance of the related object and relate the object you're editing to that. Because you're adding a new object it will be blank to start. If you want to be able to edit existing related objects from another object's admin you need to use inlines.
In your app's admin.py have something like:
from django.contrib import admin
from yourapp.models import Address, Classified
class AddressInline(admin.TabularInline):
model = Address
class ClassifiedAdmin(admin.ModelAdmin):
inlines = [AddressInline,]
admin.site.register(Classified, ClassifiedAdmin)
Adding search from there is really easy.
...
class ClassifiedAdmin(admin.ModelAdmin):
inlines = [AddressInline,]
search_fields = [
'field_you_want_to_search',
'another_field',
'address__field_on_relation',
]
...
Note the double underscore in that last one. That means you can search based on values in related objects' fields.
EDIT: This answer is right in that your foreignkey relationship is the wrong way round to do it this way - with the models shown in your question Classified would be the inline and Address the primary model.

How to use admin.ModelAdmin to customize the "add" popup?

The story begins with 2 models (User and ClientAccount) which are linked with an extra M2M model.
It is possible to create ClientAccount during editing User. The page will show a popup which allow you create a new ClientAccount. But the problem is: can I disable the foreign key fields of ClientAccount which is linking to User? This is quite confusing.
Code here:
class User(models.Model):
client_accounts = models.ManyToManyField('ClientAccount', related_name='+', through='UserClientAccountM2M', through_fields=('user', 'client_account'))
class ClientAccount(models.Model):
users = models.ManyToManyField('User', related_name='+', through='UserClientAccountM2M', through_fields=('client_account', 'user'))
class UserClientAccountM2M(models.Model):
user = models.ForeignKey(User, db_column='user_id')
client_account = models.ForeignKey(ClientAccount, db_column='client_id')
class UserAdmin(TimeLimitedAdmin):
class ClientAccountInline(admin.TabularInline):
model = ClientAccount.users.through
inlines = [
ClientAccountInline,
]
class ClientAccountAdmin(TimeLimitedAdmin):
class UserInline(admin.TabularInline):
model = ClientAccount.users.through
inlines = [
UserInline,
]
admin.site.register(User, UserAdmin)
I realise this is an old question, but I came across it as the most relevant question whilst looking to solve a similar problem (hide a FK inline only in the add popup). I'm sure you have left this problem well behind by now, but maybe others will find it useful.
I added the below to the ModelAdmin class that the popup is for. I check the request for the GET params only present when the popup dialog is launched, and if they are there, I loop through the inlines and remove the one I don't want.
def get_inline_instances(self, request, obj=None):
inline_instances = super().get_inline_instances(request, obj=None)
if '_to_field' in request.GET and '_popup' in request.GET:
# Popup dialog is open.
unwanted_inline = None
for inline in inline_instances:
inline_model_name = inline.opts.model.__name__
if inline_model_name == 'UnwantedModelName':
unwanted_inline = inline
if unwanted_inline:
inline_instances.remove(unwanted_inline)
return inline_instances
In your case, I would add the above to ClientAccountAdmin, removing the UserInline.
If you simply want to hide the m2m fields in the ClientAccount you can remove the line in the Admin.py
because there you explicitely say it should show the connection to the user in a tabularInline:
class ClientAccountAdmin(TimeLimitedAdmin):
class UserInline(admin.TabularInline):
model = ClientAccount.users.through
#inlines = [UserInline,]
There you say explicitely you want to have the M2M field from ClientAccount to User in an Inline, which you do not want. Get rid of it and the fields will disappear
EDIT:
The problem is that the "add..." link will always refer to the .../ClientAccount/add/?_popup=1 page which uses the default admin view for this model.

Django: combine two ForeignKeys into one field

I need to implement the following:
The user shall be presented with a form that will have a drop down choice menu consisting of property names. There are two types of properties: general properties, i.e. properties common for all users and custom properties, i.e. properties that each user has defined prior to that. The models would look something like that:
class GeneralPropertyName(models.Model):
name = models.CharField(max_length=20)
class CustomPropertyName(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=20)
The drop down menu should have all general properties and only those custom properties that pertain to the user.
First question: how to define such a model?
I need to: 1. somehow unify both properties, 2. take only those items from CustomPropertyName that pertain to the user
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
property = ForeignKey(GeneralPropertyName) ??UNIFY??? ForeignKey(CustomPropertyName)
Second, is there anything special that needs to be done with ModelForm?
class SpecDataForm(ModelForm):
class Meta:
model = SpecData
And the 3rd question is what needs to be done in the view? I will need to use inline formsets since I will have a few dynamic forms like that.
def index(request):
user = User.objects.get(username=request.user.username)
specdataFormSet = inlineformset_factory(User, SpecData, form=SpecDataForm, extra=30)
...
specdata_formset = specdataFormSet(instance=user, prefix='specdata_set')
...
Thanks.
EDIT: Adjusted juliocesar's suggestion to include formsets. Somehow I am getting the following error message: Cannot resolve keyword 'property' into field. Choices are: id, name, selection_title, user
def index(request):
user = User.objects.get(username=request.user.username)
user_specdata_form = UserSpecDataForm(user=user)
SpecdataFormSet = inlineformset_factory(User, SpecData, form=user_specdata_form, extra=30)
You can use a GenericForeignKey to handle it, but you still need more to solve your further questions about forms and view.
I have made an example of how you solve your problem (logged user can select from General properties and his Custom properties, non-logged user only can select General properties). I used model inheritance for the properties (In your sample code it seems that a CustomPropertyName is a PropertyName with other fields). I think inheritance is an easier and a more basic concept than ContentTypes and it fits to your needs.
NOTE: I remove some code like imports to simplify the code.
1) models.py file:
class PropertyName(models.Model):
name = models.CharField(max_length=20)
def __unicode__(self):
return self.name
class CustomPropertyName(PropertyName): # <-- Inheritance!!
user = models.ForeignKey(User)
def __unicode__(self):
return self.name
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
property = models.ForeignKey(PropertyName)
NOTES: The field SpecData.property points to PropertyName since all properties are saved in the PropertyName's database table.
2) forms.py file:
from django import forms
from django.db.models import Q
from models import SpecData, PropertyName
def UserSpecDataForm(user=None):
UserPropertiesQueryset = PropertyName.objects.filter(Q(custompropertyname__user=None) | Q(custompropertyname__user__id=user.id))
class SpecDataForm(forms.ModelForm):
property = forms.ModelChoiceField(queryset=UserPropertiesQueryset)
class Meta:
model = SpecData
exclude = ('user',)
return SpecDataForm
NOTES: The trick here is to generate the form SpecDataForm dynamically, by filtering properties according the user specified in the parameter.
3) views.py file:
from forms import UserSpecDataForm
def index(request):
if request.POST:
form = UserSpecDataForm(request.user)(request.POST) # instance=user
if form.is_valid():
spec_data = form.save(commit=False)
spec_data.user = request.user
spec_data.save()
else:
form = UserSpecDataForm(request.user)()
return render_to_response('properties.html', {'form': form}, context_instance=RequestContext(request))
NOTES: Nothing special here, just a call to form.UserSpecDataForm(request.user) that returns the form class and then instantiate. Also setted the logged-in user to the object returned on save since It was excluded in the form to not show in front-end.
Following this basic example you can do the same with formsets if you need it.
UPDATE:
Formset can be used by adding following code to the view:
user_specdata_form = UserSpecDataForm(user=request.user)
SpecdataFormSet = inlineformset_factory(User, SpecData, form=user_specdata_form, extra=30)
The complete project sample can be downloaded from http://ge.tt/904Wg7O1/v/0
Hope this helps
1a) have you looked into django's ContentType framework this will allow you to have generic foreign keys and you can put restrictions on what types of models are acceptable to store in.
1b) I think that the validation for accepting what type of foreign key is acceptable shouldn't be in your model but should be part of your form validation before saving.
2) If you do use a model form you're going to have to define your own custom widget for the propery field. This means you're probably going to have to write you're own render function to render the html from the field. You should also define your own validation function on the form to make sure that only the appropriate data is acceptable to save.
3) I don't think you'll have to do anything you aren't already doing in the views
Use GenericForeignKey:
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
property = GenericForeignKey('content_type', 'object_id')
You can use this to combine the two fields(type & id) into a single choice field.
One way is that you have only one model, make user nullable:
class PropertyName(models.Model):
user = models.ForeignKey(User, null=True, blank=True)
name = models.CharField(max_length=20)
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
property = ForeignKey(PropertyName)
So, if user is not set, it is a general property. If it is set, it is related to this user.
However, please note that if you need unique property names, that NULL != NULL.
Of course, the suggested GenericForeignKey solution is better for some cases.
Also, you can easily make the normal (non-model) form with that you describe and separate form logic from model logic.

Django link to existing item or create if unique in multi-model form

I have a form which uses multiple models, which have relationships. If the data for one of the forms matches an existing entry, I want to have the foreign key map to the existing entry rather than create a duplicate entry. However, is_valid() fails for the already existing entry and I'm not sure if it is safe to use the form data before it has been cleaned to do a database lookup for existence first. Perhaps I need to change the clean function to ignore the uniqueness requirement and then handle it in the view?
Here's an example, a user enters their name, city and state in a web form. If that city is already known to the database, then the foreign key for the person should just point to the existing entry. If it is a new city, it should be added to the database. So it is always a CREATE for Person, but it may or may not be a CREATE for Hometown.
models.py
class Person(models.Model):
name = models.CharField()
hometown = models.ForeignKey('Hometown')
class Hometown(models.Model):
cityName = models.CharField()
stateName = models.CharField()
mascot = models.CharField()
#If same city and state, it's the same place
class Meta:
unique_together = ("cityName", "stateName")
forms.py
class PersonForm(ModelForm):
class Meta:
model = Person
exclude = ('hometown')
class HometownForm(ModelForm):
class Meta:
model = Hometown
views.py
def newPerson(request):
if request.method == 'POST':
person = PersonForm(request.POST)
hometown = HometownForm(request.POST)
if (person.is_valid() and hometown.is_valid():
p = person.save(commit=False)
h = Hometown.objects.get_or_create(**hometown.cleaned_data)
p.hometown = h
p.save()
This code doesn't work, because hometown.is_valid() will be False if that city/state pair is already in the database. Should I override the clean() function to allow ignore the uniqueness requirement (enforcing it in the view by using get_or_create) or is that an indication that my design is fundamentally the wrong way of addressing this problem?
A similar question on stackoverflow contained the information I needed. Free-form input for ForeignKey Field on a Django ModelForm
As applied to my original question, I removed the HometownForm and instead added the relevant fields to PersonForm, then made the save() function of PersonForm handle the get_or_create() behavior for the Hometown.

Categories

Resources