How can I make an attribute associate automatically with User? django - django

I'm making a simple website using django.
I've added a 'Comment' model to make a comment section on a blog post. I'd like to print out each of the 'date_added', 'text', and 'owner' attributes in html.
class User_Comment(models.Model):
topic = models.ForeignKey(Topic, on_delete=models.CASCADE)
text = models.TextField()
date_added = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.text
I have problems with the 'owner' attribute.
owner = models.ForeignKey(User, on_delete=models.CASCADE)
if I try to make migrations with it, Django asks me to provide a default value.
It is impossible to change a nullable field 'owner' on user_comment to non-nullable
without providing a default. This is because the database needs something to populate
existing rows.
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows with a null value for
this column)
2) Ignore for now. Existing rows that contain NULL values will have to be handled
manually, for example with a RunPython or RunSQL operation.
3) Quit and manually define a default value in models.py.
If I add 'blank=True', 'null=True' parameters to the onwer attribute,
the attribute works but it doesn't automatically associate with the owner when adding a comment. So I have to go to the admin to designate the comment manually to its owner.
owner = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True)
What'd be the best solution for this ? I'd like to print the 'owner' attribute in html automatically without having to handle it manually.. Thank you so much for your time.

It may be helpful to explain exactly what has happened here.
You have added an additional field, owner, to an existing Comment model. Because there were already some existing comments, the migration process (that updates Django's understanding of the model in the database) needs to know what to do with the existing comment records that currently have no owner.
This is a one-time process purely to handle the existing records.
However, when you create a new comment, you'll also need to handle who the owner is so the model field gets filled automatically. Let's say you have a model form that takes in user comments and your view tests if a comment is being posted:
form = CommentForm(request.POST or None)
if request.method == "POST" and form.is_valid:
#create an uncommitted version of the form to add fields to
form_uncommitted = form.save(commit=False)
#here we explicitly assign the owner field to the user that made the request
form_uncommitted.owner = request.user
#then save the form data plus our added data
form_uncommitted.save()

Related

Django objects uniqueness hell with M2M fields

class Badge(SafeDeleteModel):
owner = models.ForeignKey(settings.AUTH_USER_MODEL,
blank=True, null=True,
on_delete=models.PROTECT)
restaurants = models.ManyToManyField(Restaurant)
identifier = models.CharField(max_length=2048) # not unique at a DB level!
I want to ensure that for any badge, for a given restaurant, it must have a unique identifier. Here are the 4 ideas I have had:
idea #1: using unique_together -> Does not work with M2M fields as explained [in documentation]
(https://docs.djangoproject.com/en/2.1/ref/models/options/#unique-together)
idea #2: overriding save() method. Does not fully work with M2M, because when calling add or remove method, save() is not called.
idea #3: using an explicite through model, but since I'm live in production, I'd like to avoid taking risks on migrating important structures like theses. EDIT: after thinking of it, I don't see how it could help actually.
idea #4: Using a m2m_changedsignal to check the uniqueness anytime the add() method is called.
I ended up with the idea 4 and thought everything was OK, with this signal...
#receiver(m2m_changed, sender=Badge.restaurants.through)
def check_uniqueness(sender, **kwargs):
badge = kwargs.get('instance', None)
action = kwargs.get('action', None)
restaurant_pks = kwargs.get('pk_set', None)
if action == 'pre_add':
for restaurant_pk in restaurant_pks:
if Badge.objects.filter(identifier=badge.identifier).filter(restaurants=restaurant_pk):
raise BadgeNotUnique(MSG_BADGE_NOT_UNIQUE.format(
identifier=badge.identifier,
restaurant=Restaurant.objects.get(pk=restaurant_pk)
))
...until today when I found in my database lots of badges with the same identifier but no restaurant (should not happend at the business level)
I understood there is no atomicity between the save() and the signal.
Which means, if the user have an error about uniqueness when trying to create a badge, the badge is created but without restaurants linked to it.
So, the question is: how do you ensure at the model level that if the signal raises an Error, the save() is not commited?
Thanks!
I see two separate issues here:
You want to enforce a particular constraint on your data.
If the constraint is violated, you want to revert previous operations. In particular, you want to revert the creation of the Badge instance if any Restaurants are added in the same request that violate the constraint.
Regarding 1, your constraint is complicated because it involves multiple tables. That rules out database constraints (well, you could probably do it with a trigger) or simple model-level validation.
Your code above is apparently effective at preventing adds that violate the constraint. Note, though, that this constraint could also be violated if the identifier of an existing Badge is changed. Presumably you want to prevent that as well? If so, you need to add similar validation to Badge (e.g. in Badge.clean()).
Regarding 2, if you want the creation of the Badge instance to be reverted when the constraint is violated, you need to make sure the operations are wrapped in a database transaction. You haven't told us about the views where these objects area created (custom views? Django admin?) so it's hard to give specific advice. Essentially, you want to have this:
with transaction.atomic():
badge_instance.save()
badge_instance.add(...)
If you do, an exception thrown by your M2M pre_add signal will rollback the transaction, and you won't get the leftover Badge in your database. Note that admin views are run in a transaction by default, so this should already be happening if you're using the admin.
Another approach is to do the validation before the Badge object is created. See, for example, this answer about using ModelForm validation in the Django admin.
I'm afraid the correct way to achieve this really is by adapting the "through" model. But remember that at database level this "through" model already exists, and therefore your migration would simply be adding a unique constraint. It's a rather simple operation, and it doesn't really involve any real migrations, we do it often in production environments.
Take a look at this example, it pretty much sums everything you need.
You can specify your own connecting model for your M2M-models, and then add a unique_together constraint in the meta class of the membership model
class Badge(SafeDeleteModel):
...
restaurants = models.ManyToManyField(Restaurant, through='BadgeMembership')
class BadgeMembership(models.Model):
restaurant = models.ForeignKey(Restaurant, null=False, blank=False, on_delete=models.CASCADE)
badge = models.ForeignKey(Badge, null=False, blank=False, on_delete=models.CASCADE)
class Meta:
unique_together = (("restaurant", "badge"),)
This creates an object that's between the Badge and Restaurant which will be unique for each badge per restaurant.
Optional: Save check
You can also add a custom save function where you can manually check for uniqueness. In this way you can manually raise an exception.
class BadgeMembership(models.Model):
restaurant = models.ForeignKey(Restaurant, null=False, blank=False, on_delete=models.CASCADE)
badge = models.ForeignKey(Badge, null=False, blank=False, on_delete=models.CASCADE)
def save(self, *args, **kwargs):
# Only save if the object is new, updating won't do anything
if self.pk is None:
membershipCount = BadgeMembership.objects.filter(
Q(restaurant=self.restaurant) &
Q(badge=self.badge)
).count()
if membershipCount > 0:
raise BadgeNotUnique(...);
super(BadgeMembership, self).save(*args, **kwargs)

Django: how to add constraints on the foreign key dashboard

I have the model below:
class Account(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE)
class AccountCreate(generics.ListCreateAPIView):
def get_queryset(self):
return Account.objects.filter(owner=self.request.user)
serializer_class = AccountSerializer
I want to create an account of which the owner is the same as the request.user. But when I tried to create a new account, the dashboard displayed all the users. How to make the choices only be the request.user?
As it is shown in the image with the red circles below, the owner could be wz, but I only want it to be victor.
If there is only one choice, why are you making this a field on the form in the first place? If you remove the owner field from the form, and automatically set the user as the owner in the view, wouldn't that be easier for the user, and less work for you? Furthermore, if the whole form is just that one question (and if this question has only one possible answer), wouldn't it be easier to process object creation in the view, when a button is pressed or something?

django model attribute field empty list

I'm trying to build an online forum. Right now, my forum model has several attributes and one of it is "owner", which is a ForeignKey to the user that created this forum. It also has another attribute "passcode" which makes sure that whenever an owner creates a forum, he/she has to type a passcode so that only others with the right passcode can join the forum. Now, I am trying to implement a new function such that users can choose to join existing forums; however, I am stuck.
1) My first issue is that in order to create a custom permission, I first need another model attribute that contains a list of the permissioned users. I was thinking of having a model attribute as an empty list, permissioned_users = [], so that whenever a user requests to join a forum and has the right passcode, his/her username will be appended to the list and then in my views.py I can use #user_passes_test to check if the request.user.username is in the list. However, i'm not sure if "students = []" will work such that i can do "anyparticularinstance".students.append("his name") will work.
2) How do i create a join forum function? I have fully implemented a create forum function but how do I allow users to join an existing forum? Thank you!
One Way you can achieve the permissions is by defining a Boolean field in your model, for example:
class Forum(AbstractBaseUser):
username=models.CharField(max_length=20,unique=True)
name = models.CharField(max_length=20)
email = models.EmailField(max_length=254,null=True,blank=True)
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
By extending the AbstractBaseUser in Django you can define custom permissions for users.Either from the default Admin provided by Django or may be your own custom admin, you can add or remove permissions to a particular user. For more information you can see the following link Django AbstractBaseUser
You can achieve your objective by using textfield, and then appending at the end of the textfield everytime you update the field.
Model:
permissioned_users = models.TextField(blank=True, null=True)
View:
foo = Forum.objects.get(id=id)
temp = foo.permissioned_users
temp = temp+" username"
foo.permissioned_users = temp
foo.save()
Obviously you have to do some more work, ex. when you want to check which user is given permission, you split the string using whitespace hence str.split(), then you can easily iterate through it and make your checks.

Django AttributeError Modifying Field Without Tracking Change History

I am currently running a production Django webapp that holds historic item information using the SimpleHistory feature.
I have a navigation bar that displays all current revisions of all items to click on and view their separate pages. That being said, I wanted to have the ability to select which items to show/hide in the navigation bar by updating a boolean field on the item admin pages.
So, I modified the item models to have a field to do such:
class Item(models.Model)
field1 = models.CharField()
field2 = models.CharField()
...
hide_item = models.BooleanField('Item hidden:', default=True) #don't want history on this field
reason_for_change = models.CharField()
changed_by = models.ForeignKey(User, null=True)
accepted_by = models.ForeignKey(User, null=True)
accepted_date = models.DateTimeField()
history = HistoricalRecords()
def __unicode__(self):
return self.name
def save(self, *args, **kwargs):
super(Item, self).save(*args, **kwargs)
#property
def _history_user(self):
return self.changed_by
#_history_user.setter
self.changed_by = value
After making the migrations, this field showed up in the admin pages to my delight, but unfortunately I am not able to modify this field without receiving the following error:
AttributeError: can't set attribute
C:\Python27\lib\site-packages\simple_history\admin.py in save_model, line 151
151. obj._history_user = request.user
I think it might have to do with the fact that all changes to item field need to be tracked using the SimpleHistory feature, but for this particular field I don't want to track and store the history of its changes, I just want to be able to enable and disable at will in the admin page.
I also noticed, that if I make a new instance of an Item on the webapp and check the value of hide_item on the admin item page, it is False when it should be True by default. On the contrary, if I attempt to add a new Item instance within the admin page, hide_item is set to True by default as expected...
Right now I think my best solution might be to make another model that holds hide/display information for all items and keep it separate from the item models.
Wondering if anyone might now how to accomplish this.
Thanks
Might not be the most elegant way to do it, but I ended up making a separate model that stores show/hide information and syncs with the item to be displayed.
I did this using a BooleanField for the show/hide and a readonly OneToOne(Item) field to sync with the item I want to display.
Worked well.

Adding a model object to a parent table

I am creating a form where a someone enters a network, location, and administrators of the network. This is my model --
class Administrator(models.Model):
email = models.EmailField()
name = models.CharField(max_length=100, blank=True)
class Network(models.Model):
name = models.CharField(max_length=50)
location = models.CharField(max_length=50)
administrators = models.ManyToManyField(Administrator, blank=True)
How could I create a form such that when the site admin adds an administrator to a network, it will immediately create that entry in the Administrator class and then link up to that in the administrators column?
For custom processing of your form objects use form.save(commit=False):
This save() method accepts an optional
commit keyword argument, which accepts
either True or False. If you call
save() with commit=False, then it will
return an object that hasn't yet been
saved to the database. In this case,
it's up to you to call save() on the
resulting model instance. This is
useful if you want to do custom
processing on the object before saving
it, or if you want to use one of the
specialized model saving options.
from: https://docs.djangoproject.com/en/1.0/topics/forms/modelforms/#the-save-method
So if you have a NetworkForm you could use commit=False and then check if your administrator already exists or if he needs to be created. get_or_create is really handy for this.
Then you could set administrator on your form to the newly created or fetched administrator-instance and save the form (using form.save() and form.save_m2m()).