Python Django: join view on the admin interface - python

I have a model for universities and another for contacts:
class University(models.Model):
abbrev = models.CharField(max_length=20, unique=True) # example "ASU" for Alabama State University
name = models.CharField(max_length=512, unique=True)
city = models.CharField(max_length=512)
state = models.CharField(max_length=2, choices=STATE_CHOICES) # abbreviation
region = models.CharField(max_length=2, choices=REGION_CHOICES) # examples Mid West, South Coast, etc.
type = models.CharField(max_length=3, choices=TIPO_IES_CHOICES) # public, private, etc.
class UniversityContact(models.Model):
person ...
university ... # models.OneToOneField("University") ???
When editing UniversityContact on the admin interface, I would like to be able to "pull" 'abbrev', 'state', 'region', and 'type' from University to show together with the contact info. Is that possible? They don't have to be editable from that context.
I've tried adding those fields to UniversityContact, all as "models.OneToOneField("University", related_name='...')", but they all end up showing the same value - abbrev - from the other table.
I'd like to be able to quickly sort contacts by, say, region, etc.

Displaying is easy - define a method that returns the related data on the model or the admin class, then use the method in list_display and/or readonly_fields.
For sorting, define the admin_order_field property of the method. Although list_display and readonly_fields do not support the double underscore related field syntax, admin_order_field does. So something like this:
class UniversityContact(models.Model):
# as above, plus:
def abbrev(self):
return self.university.abbrev
abbrev.admin_order_field = 'university__abbrev'
Optionally you can set the short_description attribute as well, if you don't want the default choic of the method name:
abbrev.short_description = 'abbreviation'
You didn't ask about this, but it seems worth knowing - list_filter also supports the standard related field name syntax:
list_filter = ('university__region',)
Alternatively, there's a code snippet here that claims to address it:
http://djangosnippets.org/snippets/2887/
I haven't tested that myself.

Related

Delete m2m through field from admin panel

I searched and could not find a way to do this. Here's my situation:
I have 3 classes:
Publication
Person
AuthorOrder
The last is a through class that allows me to specify the author order for a publication, as this does not normally seem possible to do.
Initially, I made Author a mandatory field (blank=False) for Publication and added a placeholder Person object to add Publications to that don't have a proper author. However, a better solution seems to be to just handle missing authors in Views appropriately. Now, I've changed the field to be optional, but I cannot seem to set the authors to empty via the admin panel. It gives me a "This field is required." error. My guess is that this is because the Person is required of the through class, but setting the Person to null/empty in the admin panel does not set the through object to null/empty.
I found a workaround. One can delete the placeholder Person object. This unsets the through class from the Publications without deleting them via cascade as they are no longer mandatory. However, this is not always a good workaround, so I hope there's a better method.
models.py
There is a lot of code, I reproduce only the minimal necessary:
#Publication
class Publication(models.Model):
authors = models.ManyToManyField(Person, blank=True, related_name="author_of", through='AuthorOrder')
#Person
class Person(models.Model):
# mandatory
firstname = models.CharField(max_length=200)
# AuthorOrder
class AuthorOrder(models.Model):
publication = models.ForeignKey(Publication)
author = models.ForeignKey(Person)
admin.py
# AuthorInline
class AuthorInline(admin.StackedInline):
model = AuthorOrder
extra = 3
# Publication
class PublicationAdmin(admin.ModelAdmin):
list_display = ("title", 'issue', "keywords")
search_fields = ['title', "keywords"]
list_filter = ['issue']
# author list via Inline
inlines = [AuthorInline]
admin.site.register(Publication, PublicationAdmin)
It's simple. To the very right of the "Author order: AuthorOrder object" line, there is a small "Delete" box. Check it and save. It's small and somewhat oddly placed, so easily gets missed.

Django: allow user to add fields to model

I am just starting with Django and want to create a model for an application.
I find Djangos feature to
- automatically define validations and html widget types for forms according to the field type defined in the model and
- define a choice set for the field right in the model
very usefull and I want to make best use of it. Also, I want to make best use of the admin interface.
However, what if I want to allow the user of the application to add fields to the model? For example, consider a simple adress book. I want the user to be able to define additional atributes for all of his contacts in the admin settings, i.e. add a fax number field, so that a fax number can be added to all contacts.
from a relational DB perspective, I would have a table with atributes (PK: atr_ID, atr_name, atr_type) and an N:N relation between atributes and contacts with foreign keys from atributes and contacts - i.e. it would result in 3 tables in the DB. right?
but that way I cannot define the field types directly in the Django model. Now what is best practice here? How can I make use of Djangos functionality AND allow the user to add aditional/custom fields via the admin interface?
Thank you! :)
Best
Teconomix
i would suggest storing json as a string in the database, that way it can be as extendable as you want and the field list can go very long.
Edit:
If you are using other damn backends you can use Django-jsonfield. If you are using Postgres then it has a native jsonfield support for enhanced querying, etc.
Edit 2:
Using django mongodb connector can also help.
I've used this approach, first seen in django-payslip, to allow for extendable fields. This provides a structure for adding fields to models, from which you can allow users to add/edit through standard view procedures (no admin hacking necessary). This should be enough to get you started, and taking a look at django-payslip's source code (see the views) also provides view Mixins and forms as an example of how to render to users.
class YourModel(models.Model):
extra_fields = models.ManyToManyField(
'your_app.ExtraField',
verbose_name=_('Extra fields'),
blank=True, null=True,
)
class ExtraFieldType(models.Model):
"""
Model to create custom information holders.
:name: Name of the attribute.
:description: Description of the attribute.
:model: Can be set in order to allow the use of only one model.
:fixed_values: Can transform related exta fields into choices.
"""
name = models.CharField(
max_length=100,
verbose_name=_('Name'),
)
description = models.CharField(
max_length=100,
blank=True, null=True,
verbose_name=_('Description'),
)
model = models.CharField(
max_length=10,
choices=(
('YourModel', 'YourModel'),
('AnotherModel', 'AnotherModel'), # which models do you want to add extra fields to?
),
verbose_name=_('Model'),
blank=True, null=True,
)
fixed_values = models.BooleanField(
default=False,
verbose_name=_('Fixed values'),
)
class Meta:
ordering = ['name', ]
def __unicode__(self):
return '{0}'.format(self.name)
class ExtraField(models.Model):
"""
Model to create custom fields.
:field_type: Connection to the field type.
:value: Current value of this extra field.
"""
field_type = models.ForeignKey(
'your_app.ExtraFieldType',
verbose_name=_('Field type'),
related_name='extra_fields',
help_text=_('Only field types with fixed values can be chosen to add'
' global values.'),
)
value = models.CharField(
max_length=200,
verbose_name=_('Value'),
)
class Meta:
ordering = ['field_type__name', ]
def __unicode__(self):
return '{0} ({1}) - {2}'.format(
self.field_type, self.field_type.get_model_display() or 'general',
self.value)
You can use InlineModelAdmin objects. It should be something like:
#models.py
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=100)
class ContactType(models.Model):
name = models.CharField(max_length=100)
class Contact(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
contact_type = models.ForeignKey(ContactType, on_delete=models.CASCADE)
value = models.CharField(max_length=100)
#admin.py
from django.contrib import admin
class ContactInline(admin.TabularInline):
model = Contact
class PersonAdmin(admin.ModelAdmin):
inlines = [
ContactInline,
]
By the way... stackoverflow questions should contain some code. You should try to do something before asking a question.

Django models kind of multi-table inheritance not working

I want to make a hierarchy over a DB design as described in "Fundamentals of database systems" from Elmasri & Navathe.
This implies that when I have some info which is shared for many classes/tables, I can put it in a main parent table and use the main table id as foreign key in the child tables, kind of a weak entity.
I tried using abstract and multitable inheritance (this last one doesn't let me specify the OneToOneField, don't know where to find this at django docs).
My example is right down here (one table per class):
'''I would like this to be abstract, because I will never instantiate it,
but could be not if needed'''
class Person(models.Model):
personId = models.IntegerField(primary_key=True)
name = models.CharField(max_length=45)
surname = models.CharField(max_length=45, blank=True)
email = models.CharField(max_length=45, blank=True)
phone = models.CharField(max_length=15, blank=True)
class Meta:
managed = False
db_table = 'person'
class Alumn(Person):
# Maybe this one down should be OneToOne.
# alumnId == personId always true for the same real world guy
alumnId = models.ForeignKey('Person', db_column='alumnId', primary_key=True)
comments = models.CharField(max_length=255, blank=True)
class Meta:
managed = False
db_table = 'alumn'
# There are more child classes (Client, Professor, etc....)
# but for the example this is enough
My target is achieving to create an Alumn in DB just with two sentences like:
a = Alumn(personId=1,name='Joe', [...more params...] , alumnId=1, comments='Some comments' )
a.save()
and having these two lines insert two rows: one for Person and one for Alumn. The alumnId attribute in this snippet up here could be omitted, because it will always be the same as the personId (I told you, like a weak entity).
I'm quite a beginner at django but I have looked at the documentation and proved some things with abstract=True in Person and not having succeeded I guess now that I should mess with the init constructors for getting the superclass built and after that build the child class.
I don't know the right path to choose but definitely want not to alter the database design. Please help.
Thanks in advance.
You don't need to have ids in your models; Django handle it automatically. Also you're not supposed to use camel case. In other words: personId should be person_id and is not necessary anyway - just remove it.
In general I avoid non-abstract inheritance with an ORM.
I don't really understand what you want to achieve but I'd suggest 2 approaches (for Person, Alumni, Professor, etc.), depending on your needs:
1. Abstract inheritance:
class Person:
class Meta:
abstract = True
# here you put all the common columns
Then:
class Alumni(Person):
# the other columns - specific to alumn
etc.
By doing this you have one table per sub-type of Person: Alumn, Professor etc.
2. Use composition:
class Alumn:
person = models.ForeignKey(Person, null=True, related_name="alumni_at")
university = ...
class Professor:
person = models.ForeignKey(Person, null=True, related_name="professor_at")
university = ...
This way you can do:
bob = Person.objects.create(first_name="bob", ...)
Alumn.objects.create(person=bob, university="univ 1")
Professor.objects.create(person=bob, university="univ 2")
Alumn.objects.create(person=bob, university="univ 2")

Get only one related object from Django ORM

Here are stripped down versions of the models I'm dealing with:
class Contact(models.Model):
contact_no = models.IntegerField(primary_key=True)
email_address = models.CharField(max_length=60, null=True)
class ContactRole(models.Model):
contact_no = models.ForeignKey(Contact, primary_key=True, db_column='contact_no')
role_code = models.CharField(primary_key=True, max_length=16)
role_scope_code = models.CharField(primary_key=True, max_length=16)
Contacts can and almost always do have many ContactRoles.
I want a list of Contacts where the role_scope_code of the related ContactRole is 'foo'. I know I can get this with:
Contact.objects.filter(contactrole__role_scope_code='foo')
What I also want, is for each Contact in the queryset to have a single .contactrole property. It would be the ContactRole with the role_scope_code of 'foo'. Instead I'm getting a set of all ContactRoles that match on contact_no, so that to get to properties of the ContactRole I have to do something like this:
contacts = Contact.objects.filter(contactrole__role_scope_code='foo')
for contact in contacts:
print contact.contactrole_set.filter(role_scope_code='foo')[0].role_code
I have to filter on role_scope_code twice! That doesn't seem DRY at all. What I'm looking for is a query that will allow me to have a set that works like this:
contacts = Contact.objects.filter(contactrole__role_scope_code='foo')
for contact in contacts:
print contact.contactrole.role_code
For the life of me I can't figure out how to tell Django to only return the related objects that match the filter I applied to the parent object.
A OneToOneField will solve this provided that a contact only have one contactrole. A OneToOneField gives you the api you are looking for. So instead of using a ForeignKey use a OneToOneField

Validating admin site save in Django

I have the following two classes defined in models.py
class Part(models.Model):
name = models.CharField(max_length=32)
value = models.CharField(max_length=32)
class Car(models.Model):
name = models.CharField(max_length=32)
parts = models.ManyToManyField(Part)
So my model allows for multiple parts, but I want the part names to be unique. For example, only one part with name "engine" should be allowed for a given car. How do I enforce this uniqueness?
Things I have looked into:
class save()
Overriding the default save() for Car doesn't help because parts isn't updated until save is hit.
class save_model()
This will not work because it always saves.
So what are my options for enforcing uniqueness of part names?
UPDATED:
Although I want only one part with name engine to be associated with a car, I still want the ability to define multiple parts with name engine, of course with different values.
So a car with can have a part (part.name=Engine and part.value=V6) and another car can have a part (part.name=Engine and part.value=V4, but a car can't have two parts that have part.name == engine.
EDIT:
class Part(models.Model):
name = models.CharField(max_length=32)
value = models.CharField(max_length=32)
class CarPart(models.Model):
car = models.ForeignKey(Car)
part_type = models.CharField(max_length=32, unique=true)
part = models.ForeignKey(Part)
class Car(models.Model):
name = models.CharField(max_length=32)
treat part_type as part type id (e.g. type='engine') for engines it will be unique
more about ForeignKey

Categories