Ok, I am working on a Django application with several different models, namely Accounts, Contacts, etc, each with a different set of fields. I need to be able to allow each of my users to define their own fields in addition to the existing fields. I have seen several different ways to implement this, from having a large number of CustomFields and just mapping a custom name to each field used by each user. I have also seem recommendations for implementing complex mapping or XML/JSON style storage/retrieval of user defined fields.
So my question is this, has anyone implemented user defined fields in a Django application? If so, how did you do it and what was your experience with the overall implementation (stability, performance, etc)?
Update: My goal is to allow each of my users to create n number of each record type (accounts, contacts, etc) and associate user defined data with each record. So for example, one of my users might want to associate an SSN with each of his contacts, so I would need to store that additional field for each Contact record he creates.
Thanks!
Mark
What if you were to use a ForeignKey?
This code (untested and for demo) is assuming there is a system-wide set of custom fields. To make it user-specific, you'd add a "user = models.ForiegnKey(User)" onto the class CustomField.
class Account(models.Model):
name = models.CharField(max_length=75)
# ...
def get_custom_fields(self):
return CustomField.objects.filter(content_type=ContentType.objects.get_for_model(Account))
custom_fields = property(get_fields)
class CustomField(models.Model):
"""
A field abstract -- it describe what the field is. There are one of these
for each custom field the user configures.
"""
name = models.CharField(max_length=75)
content_type = models.ForeignKey(ContentType)
class CustomFieldValueManager(models.Manager):
get_value_for_model_instance(self, model):
content_type = ContentType.objects.get_for_model(model)
return self.filter(model__content_type=content_type, model__object_id=model.pk)
class CustomFieldValue(models.Model):
"""
A field instance -- contains the actual data. There are many of these, for
each value that corresponds to a CustomField for a given model.
"""
field = models.ForeignKey(CustomField, related_name='instance')
value = models.CharField(max_length=255)
model = models.GenericForeignKey()
objects = CustomFieldValueManager()
# If you wanted to enumerate the custom fields and their values, it would look
# look like so:
account = Account.objects.get(pk=1)
for field in account.custom_fields:
print field.name, field.instance.objects.get_value_for_model_instance(account)
Related
I am trying to build a tool that, at a simple level, tries to analyse how to buy a flat. DB = POSTGRES
So the model basically is:
class Property(models.Model):
address = CharField(max_length = 200)
price = IntegerField()
user = ForeignKey(User) # user who entered the property in the database
#..
#..
# some more fields that are common across all flats
#However, users might have their own way of analysing
# one user might want to put
estimated_price = IntegerField() # his own estimate of the price, different from the zoopla or rightmove listing price
time_to_purchase = IntegerField() # his own estimate on how long it will take to purchase
# another user might want to put other fields
# might be his purchase process requires sorting or filtering based on these two fields
number_of_bedrooms = IntegerField()
previous_owner_name = CharField()
How do I give such flexiblity to users? They should be able to sort , filter and query their own rows (in the Property table) by these custom fields. The only option I can think of now is the JSONField Postgres field
Any advice? I am surprised this is not solved readily in Django - I am sure lots of other people would have come across this problem already
Thanks
Edit: As the comments point out. JSON field is a better idea in this case.
Simple. Use Relations.
Create a model called attributes.
It will have a foreign key to a Property, a name field and a value field.
Something like,
class Attribute(models.Model):
property = models.ForiegnKey(Property)
name = models.CharField(max_length=50)
value = models.CharField(max_length=150)
Create an object each for all custom attributes of a property.
When using database queries use select_related of prefetch_related for faster response, less db operations.
We are trying to work with legacy DB Tables that were generated outside of Django and are not structured in an ideal way. We also can not modify the existing tables.
The DB uses the same user ID (pk) across all the tables, wether or not there is a record for that user ID. It also uses that ID as a PK on the other tables, rather than rely on them to auto increment their own IDs.
So imagine something like this below:
class Items(models.Model):
user_id = models.ForeignKey('User', db_column='UserID')
class User(models.Model):
user_id = models.IntegerField(primary_key=True)
class UserTypeA(models.Model):
user_id = models.IntegerField(primary_key=True) # Same Value as User
class UserTypeB(models.Model):
user_id = models.IntegerField(primary_key=True) # Same Value as User
What we thought of creating a relationship between Items and UserTypeA (as well as UserTypeB) is to create another field entry that uses the same column as the user_id.
class Items(models.Model):
user_id = models.ForeignKey('User', db_column='UserID')
user_type_a = models.ForeignKey('UserTypeA', db_column='UserID')
user_type_b = models.ForeignKey('UserTypeB', db_column='UserID')
This unfortunately returns a "db_column is already used" type error.
Any thoughts on how to better approach the way what we're trying to do?
A detail to note is that we're only ever reading from this databases (no updates to), so a read-only solution is fine.
Thanks,
-RB
I've solved a similar problem with this (this code should be put before the definition of your Model):
from django.db.models.signals import class_prepared
def remove_field(sender, **kwargs):
if sender.__name__ == "MyModel":
sender._meta.local_fields.remove(sender.myFKField.field)
class_prepared.connect(remove_field)
(Tested in Django 1.5.11)
Django uses local_fields to make the CREATE TABLE query.
So, I've just attached the signal class_prepared and check if sender equals the class I was expecting. If so, I've removed the field from that list.
After doing that, the CREATE TABLE query didn't include the field with same db_column and the error did not ocurr.
However the Model still working properly (with manager methods properly populating the removed field from local_fields), I can't tell the real impact of that.
I have the following models:
class Company(ndb.Model):
name = ndb.StringProperty(indexed=False)
# some other fields
class User(polymodel.PolyModel):
company = ndb.KeyProperty(kind=Company)
# some other fields
class Object(ndb.Model):
user = ndb.KeyProperty(kind=User)
# some other fields
Now I have a user and I want to query Objects that are associated with other Users in the same company like this:
Object.query(Object.user.company == user.company)
Of course, this doesn't work, since Object.user is a key and I cannot access anything beyond that.
Is there any way to do it? I only need the company key, I was thinking on a ComputedProperty but I'm not sure if it's the best solution. Also, it would be better to query based on any field in company.
You need to denormalize and store redundant information, as the datastore doesn't support joins.
For instance given your models above, a user can only be a member of one company, if you really need to search all objects whose user is a member of a particular company then store the company key in the Object.
Use a computed property if that works best for you.
Alternately use a factory that always takes the User as a argument and construct Object that way.
I've defined language_tuples = models.ManyToManyField(LanguageTuple) in my UserProfile. This field should be filled when regular user want to became a translator. So he should be able to choose as many as needed tuples of languages - language by language.
EDIT: Thanks to Shang Wang, now I can choose multiple LanguageTuples but I'm not able to create new LanguageTuple objects inside the form.
class Language(models.Model):
shortcut = models.CharField(max_length=40)
name = models.CharField(max_length=40)
def __str__(self):
return self.name
class LanguageTuple(models.Model):
language_from = models.ForeignKey(Language, related_name='language_from', null=True)
language_to = models.ForeignKey(Language, related_name='language_to', null=True)
def __str__(self):
return '{} to {}'.format(self.language_from, self.language_to)
So let's assume that there are multiple Language objects in database already but no instances of LanguageTuple. I want user to be able to built his own tuples (as many as he wants). So if there were languages CZ,EN,GE,SK - he can built for example these tuples: CZ-EN, EN-CZ, GE-CZ, SK-GE etc. - after choosing tuples, those tuples are created inside the database as regular LanguageTuple instances if does not exists.
The problem is that there is no form field inside the form when it is rendered. Don't know what to do with that... as you can see, I've added field - language_tuples into the form.
class TranslatorRegistrationForm(forms.Form):
IBAN = forms.CharField(max_length=40,required=True)
first_name = forms.CharField(max_length=40,required=True)
last_name = forms.CharField(max_length=40,required=True)
class Meta:
model = UserProfile
fields = (
'first_name','last_name','IBAN','language_tuples'
)
One problem I've already mentioned in comment that you need forms.ModelForm for TranslatorRegistrationForm, otherwise django won't recognize all fields you want to display.
If you want user to choose from language_tuples as well as creating new pairs, it's going to be 2 forms. One for your existing form, the other is a form for model LanguageTuple. You need to display both forms in the template, so people could choose either from the list language_tuples or fill out the form for LanguageTuple.
Now be aware that you need some logic in place to detect whether user has chosen an existing language_tuple or trying to use a newly created LanguageTuple. It's some extra steps before you save everything to database but it should be straight forward.
Good evening,
I am working on some little website for fun and want users to be able to add items to their accounts. What I am struggling with is coming up with a proper solution how to implement this properly.
I thought about adding the User Object itself to the item's model via ForeignKey but wouldn't it be necessary to filter through all entries in the end to find the elements attached to x user? While this would work, it seems quite inefficient, especially when the database has grown to some point. What would be a better solution?
From what I understand of your use case, a User can have many items and and an Item can belong to multiple users. It this s the case, using ManyToManyField seems the way to go :
class Item(models.Model):
users = models.ManyToManyField('auth.User', related_name='items')
You can then query items from a specific user like this:
# adding an item to a user
user.items.add(my_item)
# query user items
user.items.all()
user.items.filter(name__startswith='Hello')
If you want to store additional information about the relationship, such as the date were the item was linked to the user, you have to specifiy an explicit intermediate model:
class Item(models.Model):
users = models.ManyToManyField('auth.User', through='ItemUser', related_name='users')
class ItemUser(models.Model):
"""Explicit intermediary model"""
user = models.ForeignKey('auth.User')
item = models.ForeignKey(Item)
date_added = models.DateTimeField(auto_now_add=True)
To create the binding beetween a User and an Item, just instanciate the intermediate model:
binding = ItemUser(user=user, item=item)
binding.save()
assert user in item.users.all()
You could create a model UserItems for each user with a ForeignKey pointing to the user and an item ID pointing to items. The UserItems model should store the unique item IDs of the items that belong to a user. This should scale better if items can be attached to multiple users or if items can exist that aren't attached to any user yet.