Creating submodels, user friendly way - django

Django 1.9.8
There is a model. And there are submodels.
One to many relationships.
Examples: person and bank accounts.
class Person(models.Model):
name = models.CharField(max_length=200, unique=True, blank=False)
class Account(models.Model):
account = models.CharField(max_length=20, unique=True, blank=False)
owner = models.ForeignKey(Person, on_delete=models.CASCADE)
Sometimes it is much more practical to let a user create submodels not one by one, but as if in a list of input fields. S/he presses a plus sign and another input field for a new submodel is added. Then another etc. This way everything is observable, table-like. And one submit button which triggers validation and creation of submodels.
Much more friendly than creating submodels one by one.
I have prepared a prototype: https://whispering-everglades-55047.herokuapp.com/
Ajax used.
This must be a typical pattern. Could you suggest me a ready made application for this purpose. I can't find myself.

The excellent django-autocomplete-light package should be exactly what you're looking for.
It allows the end user to create owners in an AccountFormView, for example, through the use of a "+" button with 'add another' functionality (outside the admin).

Related

Possibility of linking a user/profile to a previously-created name linked to a model via a foreign key

I have a Django application where registered users can add, through an input form, details of performances of their music ensemble. This application also has a a section for composers, where they add their own composition. I'm using a custom user model, with profiles linked to user accounts:
class User(AbstractBaseUser):
email = models.EmailField(verbose_name="email", unique=True, max_length=255)
first_name = models.CharField(max_length=30, blank=True, null=True)
[...]
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
[...]
This is my 'composition' model:
class Composition(models.Model):
title = models.CharField(max_length=120) # max_length = required
composer = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
[...]
And this is my 'performance' model. The performance information links to the piece performed (performed):
class Performance(models.Model):
performed = models.ManyToManyField(Composition, blank=True)
[...]
So far, so good. Now, I'd like the performers to be able to add pieces by composers who are not (yet) registered to the website. Let's say that the performer performed a piece by John Lennon and is adding information about that performance. So, the performer will need to be able to add both John Lennon, his composition, and link the two.
The most important bit is: if the ghost of John Lennon tomorrow wants to register to the website, the administrator of the website will need to be able to easily link the compositions already added by the performers to John Lennon's newly created account. Is that possible? I suppose the key to solving this issue is changing composer = models.ForeignKey(settings.AUTH_USER_MODEL... with something else, i.e. using a intermediary model. Any suggestion will be greatly appreciated.
There are different ways to do this, you should choose one based on your taste:
When you know that the actual user is registered, just delete the old, fake user referred in the composition and replace it with the actual user.
Create a new model named something like Artist and change the composer relationship to refer to the Artist model. then, link the Artist model to the actual user with a nullable foreign key.

Editing dynamic choice fields in Django

I have a standard Django blog with a Post model, only on the model I have added a ManyToManyField for approvers, the idea being that the backend passes the post to 2 or more approvers to confirm the content before it is published.
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)
approvers = models.ManyToManyField(Approvers)
I will probably learn towards something like django-fsm to create a finite state machine for the Post model to govern whether it is draft/in approval/published, but I would like to be able to change the approvers field so that the number and order of approvers (users) can be changed dynamically by the user.
What is the best way to do this? I thought I could try and change the approvers field to a JSONField so that users can add / delete / change the order of approvers and then handle the interpretation in the frontend and write some function to interface with django-fsm, but this feels like it conflates things too much. Am I missing a simpler route?
Why not make another model to do so like
class PostApprover(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='approvers')
user = models.ForeignKey(Approver, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
To access order in which post(let say with id 5) is approved (descending).you can do like
post = Post.objects.get(id=5)
post.approvers.order_by('-created_at')
you can change the value of created_at to change the order.
Or you can also make an integer field that determines your order

Database normalization in django

I need an optimally normalized database structure to achieve the following requirement.
models.py
class Learning_Institute(models.Model):
name = models.TextField()
user = models.ManyToManyField(settings.AUTH_USER_MODEL)
class Course(models.Model):
title = models.CharField(max_length=50)
instructor = models.ForeignKey(User, on_delete=models.PROTECT, related_name='courses_taught')
institute = models.ForeignKey(Learning_Institute, on_delete=models.PROTECT, related_name='courses')
I need the instructor field in the Course table to be limited to the set of users in Learning_Institute instead of all the users in the system.
How do I achieve this on the DB level?
I don't think that you can limit in the model itself.
One of the things that you can do is on form save to have validations using form clearing methods like so
And you can create a check that does something like this:
def clean_ instructor(self):
instructor = self.cleaned_data['instructor']
if instructor.type != "instructor":
raise forms.ValidationError("The user is not instructor!")
Another option is to create another User object that will inherit User and you can call it InstrcutorUsers
I have used this tutorial to extend the user model in django
I don't know if it's suitable for your scenario but changing the relations slightly may achieve what you want.
Removing the many to many for User and create a concrete association model for it, will
at least make sure the Course can only have users that also are instructors, by design.
Consider the following model structure:
class LearningInstitute(models.Model):
name = models.TextField()
class InstituteInstructor(models.Model):
class Meta:
unique_together=('user','institute')
user = models.ForeignKey(User, on_delete=models.PROTECT)
institute = models.ForeighKey(LearningInstitute, on_delete=models.PROTECT)
class Course(models.Model):
title = models.CharField(max_length=50)
instructor = models.ForeignKey(InstituteInstructor, on_delete=models.PROTECT)
You have LearningInstitutes
A user can be an instructor with a related institute, a User can only be related to the same institute once
A Course can only have an instructor (and by that also the related institute)
Design can easily be extended to let Courses have multiple instructors.
By design the Course can only have users that are also instructors.
There is a possibility in Django to achieve this in your model class. The option that can be used in models.ForeignKey is called limit_choices_to.
First I'd very strongly recommend to rename the field user in the class LearningInstitute to users. It is a many to many relation, which means an institute can have many users, and a user can perform some work in many institutes.
Naming it correctly in plural helps to better understand the business logic.
Then you can adapt the field instructor in the class Course:
instructor = models.ForeignKey(
'User', # or maybe settings.AUTH_USER_MODEL
on_delete=models.PROTECT,
related_name='courses_taught',
limit_choices_to=~models.Q(learning_institute_set=None)
)
This is not tested and probably will need some adjustment. The idea is to get all User objects, where the field learning_institute_set (default related name, since you haven't specified one) is not (the ~ sign negates the query) None.
This has however nothing to do with normalisation on the database level. The implementation is solely in the application code, and the database has no information about that.
As suggested by #TreantBG, a good approach would be to extend the class User and create class Instructor (or similar). This approach would affect the database by creating an appropriate table for Instructor.

Optional additional data on ManyToManyField

I have a ManyToManyField in Django, and I want to save additional information for the relation. What I am doing is
class Speaker(models.Model):
name = models.CharField(max_length=50)
title = models.CharField(max_length=100, blank=True)
description = models.TextField(blank=True)
class Event(models.Model):
title = models.CharField(max_length=120)
speakers = models.ManyToManyField(Speaker, blank=True, null=True, through='Role')
class Role(models.Model):
speaker = models.ForeignKey(Speaker)
event = models.ForeignKey(Event)
role = models.CharField(max_length=50, blank=True)
As per documentation, this prevents Django from doing some automatic stuff. What is particularly annoying is that it makes the Speaker list not available when creating an Event in the admin.
I realize that in general Django does not know what to put in the Role.role field. But that is optional (blank=True). I would expect that
either Django recognizes that Role has only optional fields and lets me use the many to many relation as usual (creating the fields with an empty default), or
Django admin lets me add Speakers to a newly created event, and for each such Speaker it asks for the additional information (the value of Role.role).
The second possibility would be more useful and more general than the first. Still Django admin does none of the two: instead the speakers field is removed from the Event.
Is there a way to make Django admin behave as described above?
The solution lies in this answer. Briefly, one should use InlineModelAdmin, as documented here. This realizes exactly the second behaviour I described.

does custom user class break applications in django?

Let's say that I have subclassed User model (CustomUser) properly (as explained here: http://scottbarnham.com/blog/2008/08/21/extending-the-django-user-model-with-inheritance/)
and installed the comments app.
to access the user of a comment in the template I write:
{{comment.user}} # which provides User, not my CustomUser
and therefore,
{{comment.user.CustomProperty}} #does not work.
How can I work around it?
The ForeignKey from comments.Comment is to django's built-in User object, so querying comment.user will give you the parent object (ie: the base User model). However, django inheritance does provide a way to get the subclass version from the superclass:
{{ comment.user.customeruser }}
Which would then allow you to do:
{{ comment.user.customeruser.customproperty }}
I happen to think this is a weakness in Django's inheritance implementation since it doesn't exactly mirror the behaviour you'd expect from object inheritance in Python generally, but at least there is a workaround. Since I'm hardly rushing out to submit a patch with my version of the right behaviour, I can't complain :-)
I agree with Carl Meyer's comment: it could expensive to automatically fetch the subclass without altering the parent model's db table, and returning the instance of the subclass from the parent class query would be inconsistent with Django's promise that a queryset returns the model on which the queryset was run.
I still find in practice, however, that Django's inheritance leads to some awkward extra steps on occasion. Having used Django since 0.91, and knowing that all the different strategies for resolving object-relational mapping issues have tradeoffs, I'm very happy to have inheritance in Django now, and feel that the current implementation is excellent... so I'd hate for my original answer to be construed as a slight against the project.
As such, I thought I would edit this answer to link to an answer Carl himself provided on a solution in cases where you don't know what type the subclass is: How do I access the child classes of an object in Django without knowing the name of the child class?. He offers advice there for using the ContentType framework. Still some indirection involved, but a good, generalizable, option in the toolkit.
As you can see in the comments of that post, it is still controversially discussed, what is the best way.
I tried it by subclassing too, but I ran in many problems, while using profiles work perfectly for me.
class IRCUser(models.Model):
user = models.ForeignKey(User, unique=True)
name = models.CharField(max_length=100, blank= True, null = True )
friends = models.ManyToManyField("IRCUser", blank= True, null = True)
dataRecieved= models.BooleanField(default=False)
creating an IRCUser works like this:
>>> IRCUser(user = User.objects.get(username='Kermit')).save()
EDIT: Why are user_profiles elegant:
Let's assume, we are writing a webapp, that will behave as a multi-protocol chat. The users can provide their accounts on ICQ, MSN, Jabber, FaceBook, Google Talk .....
We are free to create a custom user class by inheritance, that will hold all the additional informations.
class CustomUser(User):
irc_username = models.CharField(blank=True, null=True)
irc_password = models.PasswordField(blank=True, null=True)
msn_username = models.CharField(blank=True, null=True)
msn_password = models.PasswordField(blank=True, null=True)
fb_username = models.CharField(blank=True, null=True)
fb_password = models.PasswordField(blank=True, null=True)
gt_username = models.CharField(blank=True, null=True)
gt_password = models.PasswordField(blank=True, null=True)
....
....
this leads to
data-rows with a lot of zero-values
tricky what-if-then validation
the impossibility, to have more the one account with the same service
So now let's do it with user_profiles
class IRCProfile(models.Model):
user = models.ForeignKey(User, unique=True, related_name='ircprofile')
username = models.CharField()
password = models.PasswordField()
class MSNProfile(models.Model):
user = models.ForeignKey(User, unique=True, related_name='msnprofile')
username = models.CharField()
password = models.PasswordField()
class FBProfile(models.Model):
user = models.ForeignKey(User, unique=True, related_name='fbprofile')
username = models.CharField()
password = models.PasswordField()
the result:
User_profiles can be created when needed
the db isn't flooded by zero-values
n profiles of same type can be assigned to one user
validation is easy
this may lead to a more cryptic syntax in the templates, but we are free to have some shortcuts in our views/template_tags or to use {% with ... %} to flavour it as we want.
I don’t think there is a way around it, because as you said, comment.user is a User, not a CustomUser.
See http://docs.djangoproject.com/en/dev/topics/db/models/#proxy-models:
QUERYSETS STILL RETURN THE MODEL THAT WAS REQUESTED
There is no way to have Django return, say, a MyUser object whenever you query for User objects. A queryset for User objects will return those types of objects. The whole point of proxy objects is that code relying on the original User will use those and your own code can use the extensions you included (that no other code is relying on anyway). It is not a way to replace the User (or any other) model everywhere with something of your own creation.
Maybe in this case, the old way of using UserProfile would be a better choice?
Sorry if I’m not helping much.