Combined forms in Django for attributes of parent model - django

Having a lot of models, I have started to factor out common blocks with the aim of my database reaching second normal form. As the application should be used by a sales team, most entries are some kind of orders. An excerpt from my models file looks like this:
class Order(models.Model):
dl = models.CharField(max_length=100)
cl = models.CharField(max_length=100)
(...)
class Setup(models.Model):
order = models.ForeignKey(Order) # could be OneToOneField()
name = models.CharField(max_length=200)
package = models.CharField(choices=(
('S', 'Small'),
('M', 'Medium'),
('L', 'Large'),
('XL', 'Extra large'),
('C', 'Custom')
), max_length=2)
server = models.ForeignKey(Webserver)
(...)
While it does not make any logical sense to keep the order details out of the model for a setup order, it helps to keep the project maintainable, since setup orders are not the only things coming in and this way you can change the order model and all other models/orders are updated too.
Here comes the problem. For attributes like Setup.server Django's default behaviour of creating a dropdown with all web servers the company can offer is totally fine. If the team decides to add another one, it can simply create another server option on another page. But for order, I would like that the OrderForm is included on the same page as the SetupForm, ideally as a separate Fieldset. After submitting the form, the new order should be added to the database and Setup.order is to be filled. I know that I can code it for this special case, but the application will contain numerous forms, so a generic solution would be better.
A possible solution could be to create a custom models.ForeignKey or models.OneToOneField with a custom option and a generic view that than renders a template with two forms and links the objects afterwards.
Did anyone have a similar problem? Does anyone know a simple, maybe even builtin solution to that?
EDIT 1
I have thought about inline formsets which were the solution to a similar question which used this example
class Contact(models.Model):
...
class Communication(models.Model):
contact = models.ForeignKey(Contact)
and by using a communication fieldset. However, this treats the class containing as the child, which is not the case in my example. But it still is an option, even if it would still have to be automated so it can be used quickly for all links between the other models.

Related

How to save a model record as a template for reuse later

I have a basic blog app that has a Post model:
class Post(models.Model):
author = models.ForeignKey(
get_user_model(), null=True, on_delete=models.SET_NULL)
title = models.CharField(max_length=30)
content = models.CharField(max_length=30)
template_specific_entry = models.CharField(max_length=30)
I need users to be able to create a Post template with template_specific_entry field values, and then other users to use these templates to create new post records, updating title and content but not template_specific_entry.
See the example use case below:
I would like to retain the original Post templates in their original form, so multiple versions of that template can be used.
My question is: what's the most efficient way to go about creating this structure?
Should I create two models, PostTemplate and Post and somehow link the template_specific_values between them?
Since this is 'row level' functionality, is it better to do this via model methods, so templates and posts are stored in the same model? E.g. def createTemplate(self): and def createPost(self): referencing the same model?
In each case how would I actually implement this?
Your drawing is a very good way to understand the problem you're trying to solve. And in fact, it's also clearly showing how your models should be constructed. You have templates and posts and each post needs to be linked to one and only one template.
You can almost see your drawing as the blueprint for your models:
PostTemplate has a ForeignKey to User (since there's an author, in your example "Author1") and has some specific characteristics (template_specific_values although I would try to name this field differently). Note that you use plural here, so I'm wondering if this should be a CharField and not something else, like an ArrayField.
Post has a ForeignKey to User (the author) and to PostTemplate, so that one template can "have" many posts, but each posts only one template.
When the user has selected a template, and then writes the post, the fk of the post gets set to the chosen template.

Django Admin Inline and Schema Design

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.

10 sites through same codebase django MTI, ABCs or EAV

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.

Django - Customizeable UserProfile

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.

Django: Structure Django Model to allow Arbitrary Fieldtypes

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.