My desire is to have a common location model, and then have the various higher level models who need a location refer to it.
I want to present my user in admin with a multiple part form (an inline) that allows them to enter the higher level info for the Publisher and Building, as well as the location information for each. The inline system doesn't seem to want to work this way.
Clearly, I am doing something very wrong, because this seems like a very standard sort of problem to me. Is my schema design borked ?
Am I stupidly using the inline system ? I don't want to do subclasses of Location for each upper level object, because I want to manipulate locations in different ways independent of whatever high-level objects own them (a mailing list, or geographic look up perhaps)
models.py:
...
class Location(models.Model):
"""
A geographical address
"""
# Standard Location stuff
address_line1 = models.CharField("Address line 1", max_length = 45, null=True, blank=True)
...
class Publisher(models.Model):
"""
Contains Publisher information for publishers of yearbooks. Replaces Institution from 1.x
"""
name = models.CharField(max_length=100, null=False, help_text="Name of publisher, e.g. University of Kansas")
groups = models.ManyToManyField(Group, help_text="Select groups that this publisher owns. Usually just one, but multiple groups are possible.")
is_active = models.BooleanField(help_text="Check this box to enable this publisher.")
location = models.OneToOneField(Location)
...
class Building(models.Model):
"""
Contains Building Information
"""
name = models.CharField(max_length=100, null=False, help_text="Name of building, e.g. Physical Sciences")
is_active = models.BooleanField(help_text="Check this box to enable this building.")
location = models.OneToOneField(Location)
...
admin.py:
...
class LocationInline(generic.GenericStackedInline):
model = Location
max_num = 1
extra = 1
class PublisherAdmin(admin.ModelAdmin):
model = Publisher
inlines = [ LocationInline,
]
class BuildingAdmin(admin.ModelAdmin):
model = Building
inlines = [ LocationInline,
]
admin.site.register(Publisher, PublisherAdmin)
admin.site.register(Building, BuildingAdmin)
I can force the inline to load and present by adding this to the Location model:
# Support reverse lookup for admin
object_id = models.PositiveIntegerField()
content_type = models.ForeignKey(ContentType)
of = generic.GenericForeignKey('content_type', 'object_id' )
But when I do this, even though I do get an inline object, and can edit it, the relationship seems backwards to me, with Location storing an id to the object that created it.
Any help is welcome, either a recommended schema change to make everything work wonderfully (as Django is so good at) or a trick to make the backwards-seeming stuff make sense.
Firstly, I think you want ForeignKey, not OneToOneField. Otherwise, you might as well just add your location fields to the Publisher and Building models. Then you'll simply get a dropdown to choose the location and a link to add a new one if needed in the building and publisher admin.
If you really want to have one location instance per building/publisher, you won't be able to edit it as an inline because an inline model needs to have a ForeignKey pointing to the parent model, unless you add the generic foreign key. This isnt 'backwards' - it's a valid option when you want an object to be able to attach itself to any other, regardless of type.
When it comes to domain model, there's no such thing as a "One Right Way" to do it, it depends on your specific application's requirements.
wrt/ your problem:
The OneToOne field limits your models to one Location per model instance, which (as Greg mentionned) is not conceptually very different from just sticking the Location's fields directly in the model. wrt/ DRY/factorisation/reuse etc, you can get this done using model inheritence too, having an abstract (or eventually concrete if it makes sense for your app) Location model.
The ForeignKey solution still restricts your Publisher and Building models to a single Location (which might - or not - be what you want), but a given location might be shared between different Publisher and / or Building instances. This means that editing one given location will reflect on all the related instances (beware of unwanted side effects here).
Using a GenericForeignKey in the Location model means that a given location instance belongs to one and only one related object. No surprinsing side-effect as with the above solution but you may have duplicate locations (ie one for the building, one for the publisher) with same values, and you won't be able to lookup all related objects for a specific location (or not that easily at least). Also, this won't prevent a Publisher or Building instance to have more than one location, which once again might be fine or not. wrt/ Location instance "storing the id" of the object they belong to, well, that's really what this design choice means : a Location "belongs to" some other object, period.
In any case, designing around the default behaviour of Django's admin app is probably not the wisest thing to do. You have to first decide what makes sense for this application (and you may have different needs for Publishers and Buildings), then possibly extend the admin to match your needs.
Related
In (a toy version of) my project, there are Owners who own any number of Objects. My models.py file looks like
class Owner(models.Model)
name = models.CharField(max_length=50)
date_of_birth = models.DateField()
class Object(models.Model)
name = models.CharField(max_length=50)
price = models.models.DecimalField(max_digits=9, decimal_places=2)
owner = models.ForeignKey(Owner)
My question relates to the change page for an Owner on the admin site, e.g.
http://mysite.com/admin/myapp/owner/1/.
Now I know that if I register Object as a TabularInline or a StackedInline, then I get an editable list of the Objects this Owner owns. However, in the real version of my project, an Object has something like 25 fields, not 2, and so neither of those options is really desirable aesthetically.
What I would really like instead is to essentially have a change-list of all the Objects an Owner owns appear on the Owner's change-page. (That way I get a nice compact listing of all the Owner's Objects, and if I need to edit the details of one, I can click on its link and edit it in its own page.) Basically I want the contents of
http://mysite.com/admin/myapp/object/?owner__id__exact=1
to appear within
http://mysite.com/admin/myapp/owner/1/.
Is there a way to do this?
PS: I'm using Django 1.4 and Python 2.7.
You can define what form class and/or fields to use in each InlineModelAdmin using these attributes, and limit the amount of input fields per object that way.
I'm creating a user profile class for my new django website, and I am trying to decide how to represent a user's physical address in my models.
Is it better practice to create a new subclass of model and reference it with a OneToOne key like
class UserProfile(models.Model):
...
address = models.OneToOneField(AddressModel)
...
class AddressModel(models.Model)
street_address = models.CharField(max_length=30)
city = models.CharField(max_length=15)
....
or is it better to create a new address field like
class UserProfile(models.Model):
...
address = AddressField(location_dict)
...
class AddressField(models.Field)
# details go here
...
I generally find it useful to have separate models if the entries might be created independently. For example, if you might end up with a collection of addresses AND a collection of users, not all of which will be linked immediately, then I'd keep them separate.
However, if all addresses in your database will always and immediately be associated with a user, I'd simply add a new field to the model.
Note: some people will tell you that it's wrong and evil to have nullable database columns, and that you should therefore have a separate model if any of your addresses will ever be None. I disagree; while there are often many great reasons to avoid nullable columns, in cases like this I don't find the inconvenience of checking for a null address any more onerous than checking whether the one-to-one model entry exists.
Like Eli said, it's a question of independence. For this particular example, I would make the address a field of UserProfile, but only if you expect to have one address per user. If each user might have multiple addresses (a home address and a vacation address, for example), then I would recommend setting up a model using ForeignKey, which models a Many-To-One relationship.
class UserProfile(models.Model):
...
class AddressModel(models.Model)
user = models.ForeignKey(UserProfile)
street_address = models.CharField(max_length=30)
city = models.CharField(max_length=15)
location = models.CharField(max_length=15) #"Home," "work," "vacation," etc.
Then many AddressModel objects can be created and associated with each UserProfile.
To answer your question, I'd say in general it's probably better to separate out the address as mentioned by other users.
I think the more you learn about database normalization the easier this question is to answer.
This article, Using MySQL, Normalisation, should help you figure out the basics of the "forms" of normalization. BTW, even though it's titled MySQL, it's really very generic for relational databases.
While you don't always need to go through all the normal-forms for all projects, learning about it really helps.
I have a django based web shop that has been evolving over the past year. Currently there's about 8 country specific shops running through the same code base, plus an API, and there's soon to be a B2B website, and a few more countries to add to the list.
Variations are needed in model structure, particularly around fields in address models, the account model, and so on.
To make matters a bit more complicated, the site is running multidb with each shop instance in a separate db. So I have a situation where I might have a base ABC model, e.g:
class Address(models.Model):
class Meta:
abstract=True
class Address_UK(Address):
class Meta:
db_table="shop_address"
class Address_IT(Address):
class Meta:
db_table="shop_address"
[etc]
Then code throughout the app to select the the correct model, e.g.
if countrysettings.country == "UK":
address = Address_UK()
elif countrysettings.country == "IT":
address = Address_IT()
The countrysettings.country is actually a separate settings class which subclasses threading.local and the country code, which also corresponds with a key in settings.DATABASES, is configured by a geolocation middleware handler. So the correct database is selected, and the model specific variations are reflected in each country database.
But there are problems to this approach:
It completely breaks syncdb and is no good for south, unless I hack ./manage.py so I can pass in the country db, instead of requiring the middleware to set it.
It seems messy. So much if countrysettings.country == "xx": code lying about, and so many sub classed models.
So I was thinking of using django-eav instead, but I foresee problems in the admin, and in particular field ordering. I know that django-eav will build a modelform for the admin that includes the eav fields, but I'd ideally want these to be displayed or hidden relevant to the country.
Also I've considered having a none abstract base class, e.g. Address, and then creating country specific variations where needed (e.g Model Table Inheritance). But then I foresee the base models getting overloaded with one2one fields to each model variant. But it would solve issues with the admin.
Another option might be to have an extra data field, and to serialise additional fields into json or csv or something and store them in this field.
I can think about a few others way to attack your problem. I believe option 1 or option 2 are the best, but one might choose option 3.
Option 1: One code base, One db, One django instance, Sites framwork: If you do not actually need a distinct db for each store, create all tables and/or all possible fields, and smartly use the sites framework for condour fields fields and models. For example: keep for each address a address_type field etc and use different fields (and tables) on the same db for each site. This makes your code more complicated but simplifies your IT a lot. Use this if the code changes between sites is very minimal. (btw - The json serialization is a good option for address).
Option2: One code base, Many DBs, Many django instances: Set many sites with the same code base but carefully use conditional settings and dynamic features of python to generate different models per site. Each site will have it's own settings-uk.py, settings-us.py etc., and will have it's own db and own models using dynamic models. For example:
from django.conf import settings
# ...
class Address(models.Model):
name = models.CharField(max_length=100)
if settings.country == "US":
state = models.CharField(max_length=2)
else:
country = models.CharField(max_length=100)
Other possible tricks for this method: Enable/disable apps via the settings; Crafting custom pythonpaths for appsin the wsgi script/manage.py script; use if settings.country=='us': import uk_x as x else: import us_x as x . See also: http://code.flickr.com/blog/2009/12/02/flipping-out/
Option3: Parallel code branches, Many DBs, Many django instances: Use git to keep a few branches of your code and rebase them with each other. Requires much more IT effort. If you are planning to have many db and many server, (and many developers?) anyway, you might find this useful.
Another options: One DB, many django instances custom settings.py per instance without sites framework.
Actually EAV can solve your issue. You can in fact use EAV to show fields that a specific to the country attribute of the current object.
Here is an example
class Country(models.Model):
name = models.CharField(_("country"), max_length=50
class Address(eav.models.BaseEntity):
country = models.ForeignKey(Country, related_name="country_attrs")
# EAV specific staff
#classmethod
def get_schemata_for_model(self):
# When creating object, country field is still not set
# So we do not return any country-specific attributes
return AddressEAVSchema.objects.filter(pk=-1).all()
def get_schemata_for_instance(self, qs):
# For specific instance, return only country-specific attributes if any
try:
return AdressEAVSchema.objects.filter(country=self.country).all()
except:
return qs
# Attributes now can be marked as belonging to specific country
class AdressEAVSchema(eav.models.BaseSchema)
country = models.ForeignKey(Country, related_name="country_attrs")
# Rest of the standard EAV stuff
class AdressEAVChoice(eav.models.BaseChoice):
schema = models.ForeignKey(AdressEAVSchema, related_name='choices')
class AddressEAVAttribute(eav.models.BaseAttribute):
schema = models.ForeignKey(AdressEAVSchema, related_name='attrs')
choice = models.ForeignKey(AdressEAVChoice, blank=True, null=True)
Here how to use it:
When you create Address attributes you now have also to specify which country they belong to.
Now, when you create new Address object itself (say in admin), save, and then go back editing it you see additional country-specific attributes that match objects country.
Hope this helps.
So I've got a UserProfile in Django that has certain fields that are required by the entire project - birthday, residence, etc. - and it also contains a lot of information that doesn't actually have any importance as far as logic goes - hometown, about me, etc. I'm trying to make my project a bit more flexible and applicable to more situations than my own, and I'd like to make it so that administrators of a project instance can add any fields they like to a UserProfile without having to directly modify the model. That is, I'd like an administrator of a new instance to be able to create new attributes of a user on the fly based on their specific needs. Due to the nature of the ORM, is this possible?
Well a simple solution is to create a new model called UserAttribute that has a key and a value, and link it to the UserProfile. Then you can use it as an inline in the django-admin. This would allow you to add as many new attributes to a UserProfile as you like, all through the admin:
models.py
class UserAttribute(models.Model):
key = models.CharField(max_length=100, help_text="i.e. Age, Name etc")
value = models.TextField(max_length=1000)
profile = models.ForeignKey(UserProfile)
admin.py
class UserAttributeInline(admin.StackedInline):
model = UserAttribute
class UserProfile(admin.ModelAdmin):
inlines = [UserAttibuteInline,]
This would allow an administrator to add a long list of attributes. The limitations are that you cant's do any validation on the input(outside of making sure that it's valid text), you are also limited to attributes that can be described in plain english (i.e. you won't be able to perform much login on them) and you won't really be able to compare attributes between UserProfiles (without a lot of Database hits anyway)
You can store additional data in serialized state. This can save you some DB hits and simplify your database structure a bit. May be the best option if you plan to use the data just for display purposes.
Example implementation (not tested)::
import yaml
from django.db import models
class UserProfile(models.Model):
user = models.OneToOneField('auth.User', related_name='profile')
_additional_info = models.TextField(default="", blank=True)
#property
def additional_info(self):
return yaml.load(self._additional_info)
#additional_info.setter
def additional_info(self, user_info_dict):
self._additional_info = yaml.dump(user_info_dict)
When you assign to profile.additional_info, say, a dictionary, it gets serialized and stored in _additional_info instead (don't forget to save the instance later). And then, when you access additional_info, you get that python dictionary.
I guess, you can also write a custom field to deal with this.
UPDATE (based on your comment):
So it appears that the actual problem here is how to automatically create and validate forms for user profiles. (It remains regardless on whether you go with serialized options or complex data structure.)
And since you can create dynamic forms without much trouble[1], then the main question is how to validate them.
Thinking about it... Administrator will have to specify validators (or field type) for each custom field anyway, right? So you'll need some kind of a configuration option—say,
CUSTOM_PROFILE_FIELDS = (
{
'name': 'user_ip',
'validators': ['django.core.validators.validate_ipv4_address'],
},
)
And then, when you're initializing the form, you define fields with their validators according to this setting.
[1] See also this post by Jacob Kaplan-Moss on dynamic form generation. It doesn't deal with validation, though.
I'd like to make a user profile app in Django (I know there are some that exist, thanks) and I'm wondering how you would structure the models to allow for an arbitrary combination fields in each sub-section.
As an example, the section 'education' may have a sub-section called 'Programming Experience', and the section 'personal info' may have a sub-section called 'favourites'.
Thinking in terms of a typical side bar navigation setup each section would be a header, and each sub-section would be a link to a form where the information can be manipulated.
Education
- Schooling
- Programming Experience
Personal Info
- Location
- Favourites
- Look a-likes
What I'd like to do is be able to add items to the sub-sections on an Arbitrary basis. Whatever the feel of the site calls for.
Maybe one site would benefit from photos of the school a user attended, while another might only need a description.
I'd like to use the admin interface to add these field types to the sub-section. So adding an item would present the choice of what type of information it is (image, video, text, etc) and what sub-section it's to be applied to.
I'd like to know how you would accomplish this; and more importantly, by jumping through as few hoops as possible.
Thanks.
Edit:
To hopefully clarify the question I'll provide a sample models.py file. This is just a quick moch-up to demonstrate the problem more accurately. I have two solutions in mind, and I think solution two will work better than solution one; but I'd also like to here what the SO community thinks and if they have any other solutions of their own.
**models.py**
class Section(models.Model):
"""
The root of categorization. Acts much like a header
"""
name = models.CharField(max_length=30)
description = models.CharField(max_length=255)
class SubSection(models.Model):
"""
The contents of each section. Contains many items of varying types as needed
by the site developer.
"""
name = models.CharField(max_length=30)
description = models.CharField(max_length=255)
section = models.ForeignKey(Section)
class Item(models.Model):
"""
I would like this to store the information here and have a foreign key to the
'SubSection' table. The problem is that there are a lot of different information
types that can be stored and I'd need a row for each type. Thus for each
entry most of the columns will be blank.
I'm thinking that it may be better to use this table as a pointer to another
table that actually contains the information. This will result in a lot of
tables but will eliminate waste.
"""
name = models.CharField(max_length=30)
description = models.CharField(max_length=255)
sub_section = models.ForeignKey(SubSection)
### Solution One
# Storing the info here results in a lot of wasted space and may not be all
# that flexible
image = models.ImageField()
text = models.CharField(max_length=255)
numeric = models.IntegerField()
time = models.TimeField()
# etc ...
### Solution Two
# Storing the field info results in more tables but allows for a better match
# of information and field type.
field_type = models.CharField(max_length=255)
field_properties = models.CommaSeparatedIntegerField(max_length=None)
### Solution Two Tables
# Solution two would require a table for each field type supported here, which
# is quite a few different types.
class ImageStorage(models.Model):
item = models.ForeignKey(Item)
information = models.ImageField()
class IntegerStorage(models.Model):
item = models.ForeignKey(Item)
information = models.IntegerField()
### etc ...
Just keep in mind it's targeted at user profiles. So a weight loss site may want the users current weight in the profile (numeric information) while a travel site may want a list of places visited (text information, could even use the IPAddressField I suppose). I just have no idea what will pop up so I'm trying to make it as generic as possible.
If I'm understanding you properly, the simplest way to do this would likely be a many-to-one relationship. I'm not sure if you wanted these on a per-user or per-site basis so I'll assume you want to customize it per-site (switching that is easy).
Create a table that looks something like this:
class Section(models.Model):
section = models.CharField()
sub-section = model.CharField()
site = models.ForeignKey(Site)
For multiple subsections that belong to the same section, simply give them the same primary section name so that you can query the DB for the accompanying subsections using that name.
Another way would be to use two tables to accomplish the same thing, but given the application I think this might be more appropriate.