Adding instances into a different model - python

I am trying to make a notification app for my Django project.
I have this as one of my views:
class LikePostToggle(RedirectView):
def get_redirect_url(self,pk):
obj = get_object_or_404(UserPost,pk=pk)
user = self.request.user
if user.is_authenticated():
if user in obj.likes.all():
obj.likes.remove(user)
else:
obj.likes.add(user)
#Add notification to UserNotification model
#Auto fill all fields
return obj.get_absolute_url()
And this is my UserNotification model:
class UserNotification(models.Model):
user = models.ForeignKey(User,related_name='user',null=True)
post = models.ForeignKey('feed.UserPost',related_name='post-notification')
timestamp = models.DateTimeField(auto_now_add=True)
notify_type = models.CharField(max_length=6)
read = models.BooleanField(default=False)
def __str__(self):
return self.user
In the model I think I want the user field to be the user committing the action (Liking, commenting, etc.).
My question is how might I go above making it so that whenever someone likes a post or comments and therefore triggers the LikePostToggle view it also adds an instance to the UserNotification model so that the user can be notified?

You can create an instance with UserNotification() then call save(), or you can use the create shortcut.
In the view you have access to the post and the logged in user. The timestamp will be added automatically and read will default to False, so you just have to decide what to set notify_type to:
obj.likes.add(user)
#Add notification to UserNotification model
notification = UserNotification.objects.create(
user=self.request.user,
post=obj,
notify_type="???"
)

Related

How to get the user from a manytomanyfield in Django using Django singals and creating another object with the recived manytomany user field

I want to know is there any way where we can get the updated many to many field in one model using signals and post them to another model
class Post(models.Model):
name=models.CharField(max_length=24)
nc=models.ManyToManyField(settings.AUTH_USER_MODEL)
def __str__(self):
return self.name
# return self.name
m2m_changed.connect(receiver=like_post,sender=Post.nc.through)
I want to collect the updated record in the nc field of post model and using signals I want to create an object using function
here is the signal that connects to Post model
def like_post(sender, *args, **kwargs):
# if kwargs['action'] in ('post_update'):
if kwargs['action'] in ('post_add', 'post_remove','post_update'):
print(kwargs['instance'])
instance = kwargs['instance']
print(instance)
notify = Notify.objects.create(
recipient=instance,
creator=Post.objects.get(pk=50),
state='unread',
type=kwargs['action'],
)
else:
print('no instance')
in the recipient and the creator section I want to update those fields with an existing user object the creator is the person who updated the manytomanyfield and the recipient is the person who created that post
notify model:
class Notify(models.Model):
recipient = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='notify_recipient',on_delete=models.CASCADE)
creator = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='notify_sender',on_delete=models.CASCADE)
state = ('read', 'unread', 'unseen')
type = models.CharField(max_length=30, blank=True)
url = models.CharField(max_length=50, blank=True)
whenever I run this the instance just prints the post object name and fires this error
ValueError at /admin/posts/post/50/change/
Cannot assign "<Post: ok>": "Notify.recipient" must be a "User" instance.
You can see that your Notify class defines receipent as a ForeignKey element to the AUTH_USER_MODEL, but you are creating a Notify in your signal as:
notify = Notify.objects.create(
recipient=instance,
creator=Post.objects.get(pk=50),
state='unread',
type=kwargs['action'],
)
Here, the instance is an instance of Post rather than User, also you are using post instance in the creator field too. This is what causes the error.
To solve this error, you need to pass the user instance in those fields. For example, you can use something like:
notify = Notify.objects.create(
recipient=instance.nc, # find out how to pass the user of the post here
creator=Post.objects.get(pk=50), # replace by an user instance here instead of a post instance
state='unread',
type=kwargs['action'],
)
EDIT:
To make sure that the user instance is saved you need to override the save_model method of your ModelAdmin for post model as:
class PostAdmin(admin.ModelAdmin):
def save_related(self, request, form, formsets, change):
if not form.cleaned_data['nc']:
form.cleaned_data['nc'] = [request.user]
form.save_m2m()

Display a different default value in Django CreateView according to current logged in user?

Long story short, I am creating a ModelForm which will be passed to a generic CreateView. This form will allow a user to post an event with a location. I have already collected the user's base address in my user creation form, and my Event model has a foreignkey to the author of the event.
I would like to display the city and state of the currently logged in user as a default value on the event creation form. Since default values are set at the model level, I am not able to use the requests framework. Solution such as this one offer a way to save info from the request to the database upon submission but I would like to display this default when the user first navigates to the form page. How do I achieve this functionality?
Edit I would like to be able to pass an initial parameter as in this post but have it dynamically determined by the current logged in user.
Here is the essential part of the model. Note that BusinessUser has city and state fields.
class Job(models.Model):
author = models.ForeignKey(BusinessUser, on_delete = models.CASCADE)
location = models.CharField(max_length=200, blank=True, help_text="If different from the location listed on your company profile.")
And here is the view so far:
class JobCreation(LoginRequiredMixin, SuccessMessageMixin, generic.edit.CreateView):
model = Job
form_class = JobCreationForm
context_object_name = 'job'
success_url = reverse_lazy('jobs:business_profile')
success_message = 'New job posted!'
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
JobCreationForm is the work in progress that I'm stuck on. At the moment, it's a ModelForm giving the model and fields.
class JobCreationForm(ModelForm):
class Meta:
model = Job
fields = (
'job_title',
'location',
)
You can override get_initial if you want to set initial data dynamically. If you don't need to set it dynamically you can simply set initial.
class JobCreation(LoginRequiredMixin, SuccessMessageMixin, generic.edit.CreateView):
model = Job
form_class = JobCreationForm
def get_initial(self):
initial = super().get_initial()
initial['location'] = self.request.user.location
return initial
...

How to use deleteview for particular user and particular item in Django?

I have these models. Each reply can have none, one or more post. Post is user specific. How to make delete view so that user can only delete his post and not of posts by other on a reply.
I tried this many time but my view is deleting post of some other user as well. Means any user can delete post of any other user.
I want to make a button next to each post to delete, but button should be seen only to those who have written the post.
class Reply(models.Model):
User = models.ForeignKey(settings.AUTH_USER_MODEL)
Question = models.ForeignKey(Doubt, on_delete=models.CASCADE)
reply = models.TextField(max_length=40000)
last_updated = models.DateTimeField(auto_now_add=True)
image = models.ImageField(upload_to = upload_image_path, null = True, blank = True)
created_at = models.DateTimeField(auto_now_add=True)
def Post(self):
return reverse("community:post", kwargs={"pk": self.pk})
class Post(models.Model):
post = models.TextField(max_length=4000)
reply = models.ForeignKey(Reply, on_delete = models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
time = models.DateTimeField(null=True)
User = models.ForeignKey(settings.AUTH_USER_MODEL)
If you have the AuthenticationMiddleware enabled in settings.py, the request object in a view function will include a user model. Your view will then look something like this:
from django import http
def get_post_from_request(request):
... something to pull up the post object from the request ...
return the_post
def delete_post(request):
the_post = get_post_from_request(request)
if request.user == the_post.User:
the_post.delete()
return http.HttpResponseRedirect("/your/success/url/")
else:
return http.HttpResponseForbidden("Cannot delete other's posts")
If you're using generic class based views, your view might look more like this:
from django.views.generic import DeleteView
from django import http
class PostView(DeleteView):
model = Post
success_url = '/your/success/url/'
# override the delete function to check for a user match
def delete(self, request, *args, **kwargs):
# the Post object
self.object = self.get_object()
if self.object.User == request.user:
success_url = self.get_success_url()
self.object.delete()
return http.HttpResponseRedirect(success_url)
else:
return http.HttpResponseForbidden("Cannot delete other's posts")
If you'd like help navigating class based views (they have a dense inheritance hierarchy) I can recommend http://ccbv.co.uk - their breakdown on the Delete view is here

NOT NULL constraint failed: Trying to save formset

Note: Django/Python beginner, hope this question is clear.
I'm creating a form where multiple instances of a model (Guest) can be edited at once in a single form, and be submitted at the same time.
This Guest model is linked to a parent model, Invite, meaning multiple Guests are attached to a single Invite.
I've managed to create a formset for each Guest and display it, but when I submit it, I get the following error.
NOT NULL constraint failed: app_guest.invite_id
I assume this is because it doesn't have an invite to save the data to, but I can't figure out where to put this invite_id it's asking for.
Here's my view so far:
def extra_view(request, code):
# Get the specific invite
invite = get_invite(code)
# Get guests attached to this invite
guests = invite.guest_set.all()
# Get the context from the request.
context = RequestContext(request)
# Store object of guests marked as attending
guests_attending = invite.guest_set.filter(attending=True, invite=invite)
form = ExtraForm(data=request.POST or None)
if form.is_valid():
# Save the new category to the database.
### FAILS HERE
form.save(commit=True)
# Go to Confirm page
HttpResponseRedirect('confirm')
else:
# The supplied form contained errors - just print them to the terminal for now
print form.errors
if guests_attending.count() > 1:
# If the request was not a POST, display the form to enter details.
guest_formset = modelformset_factory(Guest, form=ExtraForm, extra=0, max_num=guests_attending.count())
# Filter the formset so that only guests marked as attending are
filtered_guest_form = guest_formset(queryset=guests.filter(attending=True))
# Return the view
return render_to_response('weddingapp/extra.html', {'GuestForm': filtered_guest_form, 'invite': invite, 'guests_attending': guests_attending, 'form_errors': form.errors}, context)
else:
# Since there's no guests to create a form for, return Confirm view
return render(request, 'weddingapp/confirm.html', {
'invite': invite,
})
Here's my Guest model:
#python_2_unicode_compatible
class Guest(models.Model):
invite = models.ForeignKey(Invite, on_delete=models.CASCADE)
guest_name = models.CharField(max_length=200)
diet = models.CharField(max_length=250)
transport = models.NullBooleanField(default=False, null=True)
attending = models.NullBooleanField(null=True)
def __str__(self):
return self.guest_name
And here's the form:
class ExtraForm(forms.ModelForm):
diet = forms.CharField(max_length=128, help_text="Please enter your diet restrictions", required=False)
transport = forms.BooleanField(initial=False, help_text="Will you be needing transport?", required=False)
# An inline class to provide additional information on the form.
class Meta:
# Provide an association between the ModelForm and a model
model = Guest
fields = ('diet', 'transport')
Any help would really be appreciated. Even advice about a better way to structure all this would be helpful. Thanks.
Your form doesn't include the invite field, so you have to set it before saving to the database.
if form.is_valid():
# Save the new category to the database.
instance = form.save(commit=False)
instance.invite = invite
instance.save() # save to the db, now that we have set the invite
...

Set field value in Django Form clean() method, if this field not passed in constructor

I need set field value, not passed to Django Form constructor.
I have model and form like this:
class Message(models.Model):
created = models.DateTimeField()
text = models.CharField(max_length=200, blank=True, null=True)
active = models.BooleanField(default=False)
class MessageForm(forms.ModelForm):
class Meta:
model = Message
exclude = ('created', 'active')
def clean(self):
# check if user is blocked
if user.is_admin():
self.cleaned_data['active'] = True
return self.cleaned_data
Expected: if current user is admin - I need automatically set message as active. User should not pass this parameter by form.
Actual: I see that saved message always have flag "False" (I can delete condition and in this case I also see that message is not active).
Please help me understand, how can I do set this "active" flag in clean() method.
The previous answer would work, but I like encapsulating all the form's internal operations like what to show and what not, within the form. I know you mentioned you don't want to send a field value to the constructor, but if you don't mind sending the user, your solution would work.
i.e., your constructor:
def __init__(self, user):
self.user = user
super(BaseForm, self).__init__()
then in your clean, you just change the user to self.user.
There is another added benefit to this. Say tomorrow you want to assign more fields based on your user, you don't need to add anything to the views, you can simply add it to the form.
EDIT:
When you add a field to exclude, it is not available in the cleaned data. Instead, set its widget as hidden.
active = forms.BooleanField(widget=forms.HiddenInput)
EDIT 2: If you really don't want the field in the form
In this case, instead of overriding the clean, why don't you override the save?
def save (self):
super(BaseForm, self).save()
if user.is_admin():
self.instance.active=True
super(BaseForm, self).save()
Don't do this in the form's clean() method, do this in the view.
def your_view(request):
if request.method == 'POST':
form = MessageForm(data=request.POST)
if form.is_valid():
new_message = form.save(commit=False)
if user.is_admin():
new_message.active = True
However, if you also want to handle the case where your user is not admin using the same form, you can look at incorporating similar logic in the form's init() instead of the view, probably by passing info about the user from the view to the form's init()
Use this:
def message_form_factory(user):
class MessageForm(forms.ModelForm):
def clean(self):
# check if user is blocked
if user.is_admin():
self.cleaned_data['active'] = True
return self.cleaned_data
return MessageForm
And in your view use:
form = message_form_factory(request.user)()
form = message_form_factory(request.user)(request.POST)

Categories