Django

One Object per User in a Model

Sometimes, just sometimes, we want our users to add only one object per user per model. As in, when they add an object to a model, the can only add another object, ONLY after their first object is deleted.

Let’s jump right into some code. Let’s use this model as an example:

class Package(TimeStampedModel):
    name = models.CharField(max_length=500)
    weight = models.IntegerField()
    createdBy = models.ForeignKey(User, on_delete=models.CASCADE)

At any point in time, I want to ensure a user only has only a single Package object in the model.

Check my article about Extending the Django User Model like a Pro.

A Possible Approach

class PackageAdd(LoginRequiredMixin, CreateView):
  model = Travel
  fields = ['name', 'weight']

  def form_valid(self, form):
    form.instance.createdBy = self.request.user
    return super(PackageAdd, self).form_valid(form)

  def get_context_data(self):
    context = super(PackageAdd, self).get_context_data()
    context['is_having_package'] = Package.objects.filter(createdBy=self.request.user).exists()
    return context

Here’s the logic behind the approach above:

  • A context is passed on to the template, with a True or False value.
    • This value is determined via checking to see if the currently logged in user has an item within the Package model and returning a Boolean

This context variable can, therefore, be checked in the template and used in rendering the template for sending to the client.

So, in the template, we can trap and make a decision with the context data in a way like this:

    <!-- Using Bootstrap 4 -->
    <div class="card">
        <div class="card-block">
          {{ is_having_package|yesno:"How about a package at a time?,Add your Package" }}

          {% if not is_having_package %}
          <form class="" method="POST" action="">
            {% csrf_token %} {% bootstrap_form form %}
            <div class="form-group">
              <input type="submit" role="button" name="Save" value="Add Package" class="btn btn-primary">
            </div>
          </form>
          {% else %}
            <p class="lead">
              You have a package listed already. Clear it first before adding another package.
            </p>
          {% endif %}
        </div>
      </div>
    </div>

If you’re wondering what all that yesno magic was about, well, you can think of it as same as:

<h4 class="card-title">
{% if not is_travel_pending %}
   How about a travel a time?
{% else %}
   Add your Travel
{% endif %}</h4>

except the yesno built-in Django tag is just too clean to not use! However, it ain’t a magic bullet for all if/else scenarios, since it is designed for strings.

“Maps values for True, False, and (optionally) None, to the strings “yes”, “no”, “maybe”, or a custom mapping passed as a comma-separated list, and returns one of those strings according to the value”

Downsides to Approach

  • Of course, this approach does the checking template-side only. If however done, a user manages to send in a valid form to the server, it will save.
    • How that will happen is something I’m keen to learn.
  • There isn’t any extra check at the model level. If you’re sending the form via an API and bypassing the views.py somehow, users can save as many objects as they want.
  • Too tedious to do for multiple models. If a single-case model, the above approach might be a quick fix. However, if you want one object per user per model for 20 models, I doubt the above strategy is worth it. You might have to look into Django packages able to apply model level user restrictions.

Conclusion

I hope you found this straightforward approach useful. I hope to catch you in the next one.

Related Articles

Back to top button