2

I have a django model which is basically a group called Contexts.It contains some fields like name, description and a user.Below is the model defined

class Contexts(models.Model):
    context_name = models.CharField(max_length=50)
    context_description = models.TextField()
    users = models.CharField(max_length=255, null=False)

Currently there is only one user per Context.But I want to add more users to the same Context.So now I want to change the users field to an array field.By the way I am using django + postgres.

So this is what I do

class Contexts(models.Model):
    context_name = models.CharField(max_length=50)
    context_description = models.TextField()
    users = ArrayField(ArrayField(models.TextField()))

But then how do I append users to the users field?This is what I do normally to add a context

@csrf_exempt
def context_operation(request):
    user_request = json.loads(request.body.decode('utf-8'))
    if request.method == "POST":
        try:
            if user_request.get("action") == "add":
                print("add")
                conv = Contexts.objects.create(
                    context_name=user_request.get("context_name"),
                    context_description=user_request.get("context_description"),
                    users=user_request.get("user")
                )

        except Exception as e:
            print("Context saving exception", e)
            return HttpResponse(0)
        return HttpResponse(1)

But how do I append one user at a time to the users field in the same context (assuming same context name is passed)?

Souvik Ray
  • 2,899
  • 5
  • 38
  • 70
  • 1
    Usually it is better *not* to store things as an array. First of all, not all databases *have* arrays, and furthermore, it usually will only create more trouble to do (efficient) querying. You usually store many-to-relations in a separate table. Django has support for this: a `ManyToManyField`. – Willem Van Onsem Aug 17 '18 at 10:20

2 Answers2

4

Usually it is better not to store things as an array. First of all, not all databases have arrays, and furthermore, it usually will only create more trouble to do (efficient) querying, especially if the elements in the array refer to other objects. You usually store many-to-relations in a separate table. Django has support for this: a ManyToManyField [Django-doc].

Furthermore the code probably already has a problem: you store users as a CharField. Now imagine that a user changes their username, then there is no longer a link here. If you want to refer to objects in (another) model, you should use relations, like a ForeignKey, OneToOneField, or ManyToManyField.

So we can probably rewrite it to:

from django.db import models
from django.conf import settings

class Contexts(models.Model):
    context_name = models.CharField(max_length=50)
    context_description = models.TextField()
    users = ManyToManyField(settings.AUTH_USER_MODEL)

The nice thing is, we no longer need to care about how Django represents this (efficiently), we can simply obtain all the Users of a some_context with some_context.users.all(). Those are then User objects (or other model objects if you later change the user model).

We can then add a User to an objects as follows:

@csrf_exempt
def context_operation(request):
    user_request = json.loads(request.body.decode('utf-8'))
    if request.method == "POST":
        try:
            if user_request.get("action") == "add":
                print("add")
                conv = Contexts.objects.create(
                    context_name=user_request.get("context_name"),
                    context_description=user_request.get("context_description"),
                )
                my_user = User.objects.get(username=user_request.get("user"))
                conv.users.add(my_user)

        except Exception as e:
            print("Context saving exception", e)
            return HttpResponse(0)
        return HttpResponse(1)

So we can fetch the user, and add it to the field. If you user_request.get('user') contains the primary key of the user, we can even omit fetching the User object, and use:

@csrf_exempt
def context_operation(request):
    user_request = json.loads(request.body.decode('utf-8'))
    if request.method == "POST":
        try:
            if user_request.get("action") == "add":
                print("add")
                conv = Contexts.objects.create(
                    context_name=user_request.get("context_name"),
                    context_description=user_request.get("context_description"),
                )
                # if user_request.get('user') contains the *primary* key of the User model
                conv.users.add(user_request.get("user"))

        except Exception as e:
            print("Context saving exception", e)
            return HttpResponse(0)
        return HttpResponse(1)
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • hey this is great.But the problem is I can't run django migrations in the server for some reason.So I am using postgres in addition to django model.But I am having tough time to define the same `Contexts` model as a postgres table.I have asked a similar question in stackoverflow https://stackoverflow.com/questions/51810439/how-to-define-djangos-many-to-many-field-in-postgres?noredirect=1#comment90576223_51810439 but haven't got any success yet.It would be great if you could give some directions on this one. (Upvoting your answer) – Souvik Ray Aug 17 '18 at 10:45
  • by the way I have used django's User model. – Souvik Ray Aug 17 '18 at 10:46
  • 2
    "I can't run django migrations in the server" => then THIS is the real problem you should address instead of using a non standard non-normal "feature". If the reason you can't run migrations on the production server is that a db admin forbid it, then you want to get in touch with him to discuss the problem and find out how to do the required schema changes. I never met a db admin that would prefer denormalized data over a clean schema change, specially when the schema change only requires adding a m2m table, which is practically costless. – bruno desthuilliers Aug 17 '18 at 10:55
  • @SouvikRay: I think it is better to first try to fix the migration problem (effectively) than trying to "circumvent" the problem by using a different column type. Sure it might for now work, but it will cause a lot of problem later by not solving the "core issue". – Willem Van Onsem Aug 17 '18 at 10:57
0

In this case you can do two ways either use the postgres json field or go with django ManyToManyField as below,

from django.db import models
from django.contrib.postgres.fields import JSONField

# users field like below

users = JSONField()

or

users = models.ManyToManyField(User)