Django: allow user to add fields to model - python

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.

Related

Django ForeignKey create

I want to assign many Region to the UserProfile model, how to do it?
the code
class Region(models.Model):
name = models.CharField(max_length=30)
created_at = models.DateTimeField(auto_now=True)
class UserProfile(models.Model):
user = models.OneToOneField(
region = models.ForeignKey(Region, on_delete=models.CASCADE, null=True, blank=True)
The relation you describe is not a ForeignKey, which means that a UserProfile has (at most) one related Region, but a ManyToManyField [Django-doc].
A ManyToManyField thus means that a region can be related to zero, one, or more UserProfiles, and a UserProfile can be related to zero, one, or more Regions.
You can thus change the models to:
class Region(models.Model):
name = models.CharField(max_length=30)
created_at = models.DateTimeField(auto_now=True)
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
regions = models.ManyToManyField(Region)
In a relational database this is implemented by adding an extra (hidden) table with ForeignKeys to Regions and UserProfiles. But the Django ORM works in a "transparant" way and thus hides the implementation details.
See the documentation for more information on how to "populate" such relation.
from django.contrib.auth.models import AbstractUser
class UserProfile(AbstractUser):
regions = models.ManyToManyField(Region,related_name='User')
I think this is the ideal way to implement what you need. Using ManyToManyField allows you to map userprofile object to more than one region object and vice versa.
Also, Inheriting Abstract User allows you to add region field to Django User Table, which is better than creating another table for linking user to and region field.

How to make a django model "commentable", "likeable" and "rateable"

I am using Django 2.0.8 and Python 3.5 for a project. I have different models in my project, some of which, I want to allow commenting on - with both the object (e.g. a blogpost) and comments to the blogpost being likeable.
I am using the threaded comments django app to provide commenting functionality.
Assuming I have a model Foo (see below):
from django.db import models
from django.conf import settings
class Foo(models.Model):
owner = models.ForeignKey(settings.AUTH_USER_MODEL, blank=False, null=False, default=1, on_delete = models.PROTECT)
# ...
class Likeable():
pass
class Rateable():
pass
How could I use mixins (or any other mechanism for that matter), to make the object Foo "commentable" (i.e. an object which can be commented upon), "likeable" (i.e. an object which can be commented upon) and "rateable" (i.e. an object which can be rated?)- bearing in mind that comments on an objects may be BOTH liked and rated.
According to django documentation , you can achieve this using the Content types Framework. ContentType is a generic model that permits you to track all the models included in INSTALLED_APPS using for that their app_label, model_name and pk. The way it works is easy:
Your generic Comment model
from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.conf import settings
class Comment(models.Model):
# Generic relation fields
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
# Model specific fields
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
comment = models.TextField()
created = models.DatetimeField(auto_now_add=True)
# ...
Your reusable generic relation model. The best way is using abstract model classes or mixins. For example, using abstract models:
from django.db import models
from django.contrib.contenttypes.fields import GenericRelation
class Commentable(models.Model):
comments = GenericRelation(Comment)
class Meta:
abstract = True
Your Commentable model:
from django.db import models
class Foo(Commentable, Likeable, ...):
# your stuff
How to use it:
# Add a new comment to Foo
foo = new Foo()
foo.save()
foo.comments.create(author=author, comment="Your comment")
# Retrieve all comments from an specific user no matter the base model
comments = Comment.objects.filter(author=author)
EDIT As #ozren1983 said, each approach has its own downsides, but this is the standard way to do it.
The main advantages are:
You can retrieve all the comments (for example) made in all your commentable models in just one query. Using the approach of having a comment, like, etc table per model, you would need to concatenate a query per model. This could be problematic and a bit challenging if you have a lot of models or if you want to merge the results and order them, for example.
Just one table per functionality (comments, likes) implies just one database migration in case of change. This could be key if your database is huge.
The main disadvantage is the lack of integrity checks of this generic relationship in database. But if you plan to use the django ORM strictly, nothing should be broken.
BONUS: Another approach that many projects use is inheriting the models (one to one relationship) from an specific one called Item or Thread. Then, you can add all the comments, likes, etc functionalities to this model. This is called multi-table inheritance. An example:
from django.db import models
class Thread(models.Model):
pass
class Comment(models.Model):
# Relation with thread
thread = models.ForeignKey(
Thread,
on_delete=models.CASCADE,
related_name="comments"
)
# Model specific fields
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
comment = models.TextField()
created = models.DatetimeField(auto_now_add=True)
# ...
class Foo(Thread):
pass
Unlike using the generic relationships, the main advantage of this method is that, this way, you have database integrity checks.
The main disadvantage is that your database structure could become complex.
Based on my experience and recommendations in Two scoops of Django, I would advise against using GenericForeignKey and GenericRelation. Two big downsides of that approach are:
slow queries
danger of data corruption
Instead, I would use following approach. Let's say you have 3 models:
class User(models.Model):
username = models.CharField(max_length=255)
class Author(models.Model):
name = models.CharField(max_length=255)
class Post(models.Model):
title = models.CharField(max_length=255)
author = models.ForeignKey(Author)
Add abstract Like model, and use it as base class for other models that will implement liking functionality.
class Like(models.Model):
user = models.ForeignKey(User)
date_created = models.DateTimeField(auto_now_add=True)
class Meta:
abstract = True
class AuthorLike(Like):
author = models.ForeignKey(Author)
class PostLike(Like):
post = models.ForeignKey(Post)
Similarly, add abstract Rating model and use it as a base class:
class Rating(models.Model):
user = models.ForeignKey(User)
rate = models.PositiveSmallIntegerField()
date_created = models.DateTimeField(auto_now_add=True)
class Meta:
abstract = True
class AuthorRating(Rating):
author = models.ForeignKey(Author)
class PostRating(Rating):
post = models.ForeignKey(Post)
You can use same approach to enable liking and rating to the Comments model you are using:
from threadedcomments.models import ThreadedComment
class ThreadedCommentRating(Rating):
threadedcomment = models.ForeignKey(ThreadedComment)
class ThreadedCommentLike(Like):
threadedcomment = models.ForeignKey(ThreadedComment)
The django-contrib-comments app, according to documentation, makes use of GenericForeignKey, meaning its own model can create a relation to any other model in your project.
A simple solution would be to just copy that existing functionality, creating your own Like/Rate application based on the same concept (i.e. storing the Like/Rate models in that application's models).
I think you would get very far starting out by forking the https://github.com/django/django-contrib-comments codebase.
(I assume you have searched and failed to find an already existing application that already does this).

Trying to extend AbstractUser to create multiple user types in Django

So I have been searching all around the internet for a full example of how to user AbstractUser when u have at least 2 different models. Didn't find anything conclusive.. at least that would work on latest version of Django (2.0.1).
I have 2 models, teacher and student, and registration needs to be different. Besides username, email, name and surname, I need for example, for the student, to upload a profile picture, email, phone, student_ID. And for teacher, bio, academic title and website. Did I start good ? What is the right approach ?
class Profile(AbstractUser):
photo = models.ImageField(upload_to='students_images')
email = models.EmailField()
phone = models.CharField(max_length=15, )
class Student(Profile):
student_ID = models.CharField(unique=True, max_length=14,
validators=[RegexValidator(regex='^.{14}$',
message='The ID needs to be 14 characters long.')])
def __str__(self):
return self.name
class Teacher(Profile):
academic_title = models.CharField(max_length=30)
bio = models.TextField()
website = models.URLField(help_text="E.g.: https://www.example.com", blank=True)
Your goals can be accomplished using a 'Profile' pattern. You don't necessarily need to use a custom user model for this. But you need to have a single common model to for authentication; you can use the builtin django user for this or a custom class... Your Student and Teacher models should be OnetoOne relationships. This is the recommended solution per the documentation.
If you wish to store information related to User, you can use a OneToOneField to a model containing the fields for additional information. This one-to-one model is often called a profile model, as it might store non-auth related information about a site user.
In your case, you may do something like this:
class StudentProfile(models.Model):
user = models.OneToOneField('User', related_name='student_profile')
# additional fields for students
class TeacherProfile(models.Model):
user = models.OneToOneField('User', related_name='teacher_profile')
# additional fields for teachers
Then you can create your registration forms based on these profile models.
class StudentResistrationForm(forms.ModelForm):
class Meta:
model = StudentProfile
fields = (...)
class TeacherRegistrationForm(forms.ModelForm):
class Meta:
model = TeacherProfile
fields = (...)
You can create the user instance to which the profile is related to at the same time you create the profile. You might do this with formsets, for example.
add
class Meta:
abstract = True
to profile model
and change AbstractUser to models.Model

Django DRF - What's the use of serializers?

I've been using Django for over 3 years now, but have never felt the need to use DRF. However, seeing the growing popularity of DRF, I thought of giving it a try.
Serializing is the concept I find it most difficult. Consider for eg:- I want to save user details. Following is the user related models.
class Users(models.Model):
GENDER_CHOICES = (
('M', 'MALE'),
('F', 'FEMALE'),
('O', 'OTHERS'),
)
first_name = models.CharField(max_length=255, blank=True, null=True)
middle_name = models.CharField(max_length=255, blank=True, null=True)
last_name = models.CharField(max_length=255, blank=True, null=True)
gender = models.CharField(choices=GENDER_CHOICES, max_length=1, blank=True,
null=True)
class UserAddress(models.Model):
ADDRESS_TYPE_CHOICES = (
('P', 'Permanent'),
('Cu', 'Current'),
('Co', 'Correspondence')
)
line1 = models.CharField(max_length=255)
line2 = models.CharField(max_length=255, blank=True, null=True)
pincode = models.IntegerField()
address_type = models.CharField(choices=ADDRESS_TYPE_CHOICES,
max_length=255)
user_id = models.ForeignKey(Users, related_name='uaddress')
class UserPhone(models.Model):
phone = models.CharField(max_length=10)
user_id = models.ForeignKey(Users, related_name='uphone')
class UserPastProfession(models.Model):
profession = models.CharField(max_length=10) # BusinessMan, software Engineer, Artist etc.
user_id = models.ForeignKey(Users, related_name='uprofession')
I'm getting all the user details bundled in one POST endpoint.
{
'first_name': 'first_name',
'middle_name': 'middle_name',
'last_name': 'last_name',
'gender': 'gender',
'address': [{
'line1': 'line1',
'line2': 'line2',
'address_type': 'address_type',
}],
'phone': ['phone1', 'phone2'],
'profession': ['BusinessMan', 'Software Engineer', 'Artist']
}
Without using DRF, I would have made a Users object first, linking it with UserAddress, UserPhone and UserPastProfession object.
How the same could be done using DRF? I mean validating, serializing, and then saving the details. How serializers.py file will be look like?
If you want to make your life easy, you will surely use it.
Serializers allow complex data such as querysets and model instances to be converted to native Python datatypes that can then be easily rendered into JSON, XML or other content types. Serializers also provide deserialization, allowing parsed data to be converted back into complex types, after first validating the incoming data.
This gives you a generic way to control the output of your responses, as well as a ModelSerializer class which provides a useful shortcut for creating serializers that deal with model instances and querysets.
They save you from writing a lot of custom code. Let’s look at some examples.
Pretend we have an app that tracks a list of tasks that the user has to complete by a certain date. The Task model might look something like the following:
class Task(models.Model):
title = models.CharField(max_length=255)
due_date = models.DateField()
completed = models.BooleanField(default=False)
When the user requests those tasks, our app returns a response in the form of a JSON-serialized string. What happens when we try to serialize our Django objects using the built-in json library?
import json
task = Task.objects.first()
json.dumps(task)
We get a TypeError. Task is not JSON serializable. To bypass this, we have to explicitly create a dictionary with each of the attributes from Task.
json.dumps({
'title': task.title,
'due_date': task.due_date.strftime('%Y-%m-%d'),
'completed': task.completed
})
Serializing a Python object from a JSON string or from request data is just as painful.
from datetime import datetime
title = request.data.get('title')
due_date = datetime.strptime(request.data.get('due_date'), '%Y-%m-%d').date()
completed = request.data.get('completed')
task = Task.objects.create(title=title, due_date=due_date, completed=completed)
Now, imagine having to follow these steps in multiple views if you have more than one API that needs to serialize (or deserialize) JSON data. Also, if your Django model changes, you have to track down and edit all of the custom serialization code.
Creating and using a serializer is easy:
from rest_framework import serializers
class TaskSerializer(serializers.ModelSerializer):
def create(self, validated_data):
return Task.objects.create(**validated_data)
class Meta:
model = Task
fields = ('title', 'due_date', 'completed')
# Serialize Python object to JSON string.
task_data = TaskSerializer(task).data
# Create Python object from JSON string.
task_data = TaskSerializer(request.data)
task = task_data.create()
If you update the Django model, you only have to update the serializer in one place and all of the code that depends on it works. You also get a lot of other goodies including (as you mentioned) data validation.
Hope that helps!
If I got you correctly, my answer is:
It is not necessary to write one serializer for a model, even for method type (POST,GET etc.). You can pretty much create serializers for your model as much as you need and set fields you want to operate on. You can also set those different serializers as serializer_class property of your APIView class per each method.
I strongly recommend you to take some time to look at the Django Rest Framework Tutorial
below is how your serializer can look.. but please go through this DRF serializer realtionship
from rest_framework.serializers import (
ModelSerializer,
PrimaryKeyRelatedField
)
class UserSerializer(ModelSerializer):
"""
Serializer for the users models.. Please dont forget to import the model
"""
class Meta:
model = Users
field = "__all__"
class UserPhoneSerializer(ModelSerializer):
"""
Serializer for the users address model..
Pass the previously created user id within the post.. serializer will automatically validate
it
"""
user_id = PrimaryKeyRelatedField(queryset=Users.objects.all())
class Meta:
model = UserPhone
field = "__all__"
class UserAddressSerializer(ModelSerializer):
"""
Serializer for the users address model..
Pass the previously created user id within the post.. serializer will automatically validate
it
"""
user_id = PrimaryKeyRelatedField(queryset=Users.objects.all())
class Meta:
model = UserAddress
field = "__all__"
class UserPastProfessionSerializer(ModelSerializer):
"""
Serializer for the UserPastProfession model..
Pass the previously created user id within the post.. serializer will automatically validate
it
"""
user_id = PrimaryKeyRelatedField(queryset=Users.objects.all())
class Meta:
model = UserPastProfession
field = "__all__"

Django reference a Model by foreign key or a different field

I am using Django REST Framework. I have two models, Sites and Statuses.
class Sites(models.Model):
site_id = models.AutoField(primary_key=True)
status = models.ForeignKey(Statuses, models.DO_NOTHING, blank=True, null=True)
class Statuses(models.Model):
status_id = models.AutoField(primary_key=True)
description = models.CharField(max_length=255, blank=True, null=True, unique=True)
class Meta:
managed = True
db_table = 'Statuses'
I would like to be able to perform a GET on sites, and have the Statuses.description field returned (instead of Statuses.status_id). Also, I would like it so that either status_id or description may be used interchangeably in a POST to create a new site. Where does this type of functionality belong (serializer, models, etc...)?
I know I can accomplish the first part of my question by adding a property to the Sites model and then referencing this field in the Sites serializer.
#property
def status(self):
return self.row_status.description
However I thought the convention of a Model is that it should be a 1:1 representation of the database table. Is there a better way to do this?
This fits well in the serializer, like this:
class SitesSerializer(serializers.ModelSerializer):
description = serializers.CharField(source='status.description')
class Meta:
model = Sites
fields = ('site_id', 'description')
(But the status field should probably not have null=True set.)

Categories