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
orFalse
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 value is determined via checking to see if the currently logged in user has an item within the
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 allif/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.