Django update view won't save - python

I have this update view but it will not save upon form submission. 5 minutes ago everything was working fine and then I added the edit feature for user posts and all of a sudden nothing will save when trying to edit things.
users app views:
class UserEditProfileView(LoginRequiredMixin,UpdateView):
login_url = '/login/'
model = UserProfile
fields = [
'first_name',
'profile_pic',
'location',
'title',
'user_type',
'website',
'about',
'twitter',
'dribbble',
'github'
]
template_name_suffix = '_edit_form'
def get_success_url(self):
userid = self.kwargs['pk']
return reverse_lazy('users:user_profile',kwargs={'pk': userid})
users app models:
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
first_name = models.CharField(max_length=50,default='User')
join_date = models.DateTimeField(default=timezone.now)
profile_pic = models.ImageField(upload_to='profile_pics',null=True,blank=True)
location = models.CharField(max_length=150)
title = models.CharField(max_length=250)
user_type = models.IntegerField(choices=USER_TYPE_CHOICES,default=1)
website = models.URLField(max_length=100,blank=True)
about = models.TextField(max_length=500,default='about')
twitter = models.CharField(max_length=50,blank=True)
dribbble = models.CharField(max_length=50,blank=True)
github = models.CharField(max_length=50,blank=True)
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.userprofile.save()
def __str__(self):
return self.user.username
user profile_edit_form.html:
{% extends "users/base.html" %}
{% block content %}
<div class="form-title">
<h2 class="form-title-text">Edit Profile</h2>
</div>
<div class="user-forms-base">
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Save" />
</form>
</div>
{% endblock %}
I am having the same issue with updating posts on the home page, however I am assuming the issues are the same and so I'll be able to just replicate this solution over there.

Someone mentioned this in the comments but basically the form was returning in valid so I override form_invalid to print any errors that may have been causing it. This showed that it was sending a string when it was expecting an int at the model level. Once I switched it back to send an int the problem went away and now it works. Thanks guys.

Related

Can't change user profile image in django

I am really new to django and I am building a website for internal use in my company. I have extended the user model with another model called "profile" in order to store extra information about each user, including a profile picture.
I have set up a form.py class with the data i'd like to be able to modify:
class UserForm(forms.ModelForm):
class Meta:
model = Profile
fields = ['office','role', 'userImage']
The form in the html is as follows:
<form class="form" action="" method="POST" enctype="multipart/form-data">
{% csrf_token %}
{% for field in form %}
<div class="form__group">
<label for="Profile.userImage">{{ field.label }}</label>
{{ field }}
</div>
{% endfor %}
<div class="form__action">
<a class="btn btn--dark" href="{% url 'user-profile' request.user.id%}">Cancel</a>
<button class="btn btn--main" type="submit">Update</button>
</div>
</form>
And in the views.py, here is the function that takes care of this:
def update_user(request):
user = request.user
profile = request.user.profile
if request.method == 'POST':
form = UserForm(request.POST, request.FILES, instance=profile)
if form.is_valid():
form.save()
return redirect('user-profile', pk=user.id)
else:
form = UserForm(instance = profile)
return render(request, 'base/update-user.html', {'form': form})
And the profile model is:
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
job= models.TextField(max_length=50, blank=True, verbose_name="Centro")
role= models.TextField(null=True, max_length=50, blank=True, verbose_name="Cargo")
userImage = models.ImageField(upload_to='profileImages/', default='profileImages/happy.jpg', verbose_name="Profile Image")
#receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
#receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()
in addition, the MEDIA_ROOT and MEDIA_URL are as follows:
MEDIA_ROOT = os.path.join(BASE_DIR, 'static/images') MEDIA_URL = '/images/'
The form works fine for all fields, they both dispplay and update all the information regarding the "office" and "job" fields, but not the profile image.. It neither uploads a file to the folder I have assigned it to upload to. Even when I manually insert another image in the folder, it doesn't change it. Everything works through django admin though. Images are uploaded and changed correctly. It just wont do it via the form, no errors come up and the terminal doesn't display any issues, neither does the console in the browser.
I don't know what to do, please let me know if you need any extra information in order to diagnose the problem.
I appreciate any help provided!

How can i implement Notifications system in django

I created an app where user's can post a question and get answers from others users.
Now I want to implement a notification system, so that when a user answer a question, the author of that question will receive notification. Like social media notifications.
The home templates:
<div class="container">
<div class="row justify-content-center">
<div class="row justify-content-center">
<div class="col-md-6">
Ask Question
Notifications
FeedBack
Log Out
</div>
</div>
</div>
</div>
<div class="container">
<div class="row justify-content-center">
{% for question in list_of_question reversed %}
<div class="col-md-4">
<div class="card my-3">
<div class="card-header">
<p class="card-title">{{question.user.username.upper}}</p>
</div>
<div class="card-body">
<a href="{% url 'view-Question' question.id %}" style="text-decoration: none;">
<p class="card-title">{{question.title}}</p>
</a>
<p>Category: {{question.category}}</p>
</div>
</div>
</div>
{%endfor%}
</div>
</div>
the models:
class Question(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=100, blank=False, null=False)
body = RichTextField(blank=False, null=False)
category = models.CharField(max_length=50, blank=False, null=False)
def __str__(self):
return str(self.title)
class Answer(models.Model):
user = models.ForeignKey(User, blank=False, null=False, on_delete=models.CASCADE)
answer = RichTextField(blank=False, null=False)
post = models.ForeignKey(Question, blank=False, null=False, on_delete=models.CASCADE)
def __str__(self):
return str(self.user)
The views:
class My_Question(LoginRequiredMixin, CreateView):
model = Question
fields = ['title', 'body', 'category']
template_name = 'question.html'
success_url = reverse_lazy('index')
def form_valid(self, form):
form.instance.user = self.request.user
return super (My_Question, self).form_valid(form)
class My_Answer(LoginRequiredMixin, CreateView):
model = Answer
fields = ['answer']
template_name = 'answer.html'
success_url = reverse_lazy('index')
def form_valid(self, form):
form.instance.user = self.request.user
form.instance.post_id = self.kwargs['pk']
return super (My_Answer, self).form_valid(form)
def viewQuestion(request, pk):
question = Question.objects.get(id=pk)
answers = Answer.objects.filter(post_id=question)
context = {'question':question, 'answers':answers}
return render(request, 'viewQuestion.html', context)
the home page view:
#login_required(login_url='login')
def index(request):
query = request.GET.get('q', None)
list_of_question = Question.objects.all()
if query is not None:
list_of_question = Question.objects.filter(
Q(title__icontains=query) |
Q(category__icontains=query)
)
context = {'list_of_question':list_of_question}
return render(request, 'index.html', context)
the urls
path('index/', views.index, name='index'),
path('view/<int:pk>/', views.viewQuestion, name='view-Question'),
path('question/<int:pk>/answer/', views.My_Answer.as_view(),
name='answer'),
path('question/', views.My_Question.as_view(), name='question'),
Here is an outline for a basic notification system in Django:
Model
You need a model to store notifications. Each notification belongs to a user and has content (i.e. a text message). You also need to store whether a message has been read and a timestamp:
class Notification(models.Model):
is_read = models.BooleanField(default=False)
message = models.TextField()
timestamp = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
Creating a notification
You can then create a new notification for a user when needed, e.g. when another user answers a question you can also create a notification for the question's owner in the view.
class My_Answer(LoginRequiredMixin, CreateView):
...
def form_valid(self, form):
...
form.instance.user = self.request.user
question_author = form.instance.post.user
Notification.objects.create(user=question_author, text="New answer!")
...
return super().form_valid(form)
List of notifications
Then you need a page that lists all notifications for the current user. That can be implemented with a standard list view.
The query would look something like this:
class NotificationListView(ListView):
model = Notification
def get_queryset(self):
return Notifications.objects.filter(user=self.request.user).order_by("-timestamp")
You of course also need to define a URL and a template for this new view. We will define the URL name as notifications.
Showing new notifications to users
Finally, you need to inform users about new notifications. This can be done by checking how many unread notifications the current user has and showing a badge on the web badge. This would be part of the index view.
#login_required(login_url='login')
def index(request):
...
unread_notifications = Notification.objects.filter(user=request.user, is_read=False).count()
context["unread_notifications"] = unread_notifications
...
Then on your home page you need a link to a page that shows all notifications and a badge that shows how many unread notifications the current user has. Something like this:
<a href="{% url "notifications" %}">
Notifications
{% if unread_notifications %}
<span class="badge bg-secondary">{{ unread_notifications }}</span>
{% endif %}
</a>
Real life example
If you want to see how this is implemented in a real project, here is the link to an open source project called "Alliance Auth" that implements a portal web page and has a very similar notification architecture. The link is to the notification app within that portal: https://gitlab.com/allianceauth/allianceauth/-/tree/master/allianceauth/notifications

How can I link a comment to its corresponding post in Django?

I am building a Django project where I have an index page that lists all posts. The user can click on the name of a post and this will take them to a detail page with the complete post information (date, content, category). This detail page also has a link that will take the user to a form where they can leave a comment. Once the user clicks submit they are supposed to navigate back to the post detail page and the comment is supposed to be there. The issue I am having right now is that the comment is being automatically assigned to the first post in the index list rather than the post the user had visited (I think this may have something to do with the current default setting in my models, but how else can I get the post id?). How can I make it so that the comment is assigned to its correct post? I have tried everything with the models and views but nothing seems to work. Thank you for your help, I think the solution to this might be simple but I can't find it anywhere.
Here is my relevant models:
class UserPost(models.Model):
title = models.CharField(max_length=255)
category = models.ForeignKey('Category', on_delete=models.SET_NULL, null=True)
content = models.TextField()
created_on = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey(User, default=1,
on_delete = models.CASCADE
)
#id = models.AutoField(primary_key=True)
def __str__(self):
"""String for representing the UserPost object."""
return self.title
def get_absolute_url(self):
"""Returns the url to access a detail record for this user post."""
return reverse('userpost-detail', args=[str(self.id)])
class Comment(models.Model):
author = models.ForeignKey(User, default=1,
on_delete = models.CASCADE
)
content = models.TextField()
created_on = models.DateTimeField(auto_now_add=True)
def comment_default():
return {UserPost.id}
post = models.ForeignKey(UserPost, default= comment_default, on_delete=models.CASCADE, related_name="comments")
def __str__(self):
"""String for representing the comment object."""
return '%s - %s - %s' % (self.post.title, self.author, self.created_on)
def get_absolute_url(self):
return reverse('userpost-detail', args=[str(self.post.id)])
And my views:
class UserPostDetailView(generic.DetailView):
model = UserPost
#post = UserPost.objects.get(id=id)
#comments = Comment.objects.filter(Comment.post)
def get_context_data(self, **kwargs):
context = super(UserPostDetailView, self).get_context_data(**kwargs)
context['Comment'] = UserPost.comments
return context
class PostCreate(CreateView):
model = UserPost
fields = ['title', 'category', 'content']
class CommentCreate(CreateView):
model = Comment
fields = ['post','content']
And my html:
{% extends "base.html" %}
{% block page_content %}
<h1>Title: {{ userpost.title }}</h1>
<p><strong>Author:</strong> {{ userpost.author }}</p>
<p><strong>Content:</strong> {{ userpost.content }}</p>
<p><strong>Category:</strong> {{ userpost.category }}</p>
<a class="btn btn-primary" href="{% url 'comment-create' %}" role="button">Leave a Comment</a>
<h3>Comments:</h3>
{% for comment in userpost.comments.all %}
<p>
On {{comment.created_on.date }}
<b>{{ comment.author }}</b> wrote:
</p>
<p>{{ comment.content }}</p>
<hr>
{% endfor %}
{% endblock %}
You need to pass Post ID in your url for this.
path("comment/<int:post_id>/", CommentCreateView, name="comment-create")
Now in template
<a class="btn btn-primary" href="{% url 'comment-create' userpost.id %}" role="button">Leave a Comment</a>
Views
class CommentCreateView(CreateView):
model = Comment
fields = ['content'] # remove field post here
def form_valid(self, form):
form.instance.post_id = self.kwargs.get("post_id")
return super().form_valid(form)

Which one is more convenient to create django form, CreateView or forms.ModelForm

I am very beginner in django. I want to create a post form which be able to have title, content, image/file upload and etc.
I am very confused with concept of modelforms and createview. I tried this:
blog/view.py:
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ['title', 'content', 'imagefile']
success_url = '/blog/home/'
# template_name = 'blog/post_form.html'
def __init__(self, *args, **kwargs):
super(PostCreateView, self).__init__(**kwargs) # Call to ModelForm constructor
def form_valid(self, form):
form.instance.author = self.request.user
form.save()
return super().form_valid(form)
blog/templates/blog/post_form.html:
{% extends "blog/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="content-section">
<form method="POST">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Post::</legend>
{{ form|crispy }}
<img src='{{ post.imagefile.url }}'>
<br><br>
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Update</button>
</div>
</form>
</div>
{% endblock content %}
blog/urls.py:
from django.urls import path
from .views import (
PostCreateView,
)
urlpatterns = [
path('blog/post/new/', PostCreateView.as_view(), name='post-create')
]
blog/models.py
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
# image = models.ImageField(default='default_post.jpg', upload_to='postimages')
imagefile = models.FileField(upload_to='postimages', null=True, verbose_name="")
# if user is deleted the idea should be deleted as
author = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
# return self.title
return self.title + ": " + str(self.imagefile)
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk': self.pk})
My question: All I want is make a kind of form to create the new post with title, content, upload button and submit button. However I don't know if CreateView can be customised even for adding further items or forms.Modelforms should be used?
You would want your view to be in views.py and your form to be in forms.py. You will need both, not one or the other.
Something like:
class CreateXYZView(CreateView):
template_name = "blog/post_form.html"
model = Post
form_class = postform
...do view stuff
def post(self, request, *args, **kwargs):
Also have a forms.py file
class postform(forms.ModelForm):
class Meta:
model = Post
widgets = {}
exclude = ['fieldname']
It looks like you are using a model named Post which is a good idea. By declaring postform as a forms.ModelForm it will pull your model fields into the form I.E from class Post it pulls title, content, imagefield, etc.. unless the field is specifically added to the exclude parameter. This is useful for parameters in your model like auto fields created_by or post_date where the user should not fill these in manually. Add these to exclude so they do not show in the form.
You can also manually add form fields in your template after {{form|crispy}} but I would avoid that as it creates more work in processing the data.
If you are filling out a form that is not tied to a model you can also use forms.Form:
class SupportTicket(forms.Form):
title = forms.CharField(label="Titlte", max_length=250, widget=forms.TextInput(...)
content = forms....
E.G. use this where the information was being passed directly to GitLab and not saved locally into a model for use later.

How to create new issues without selecting 'project'(manually select project_id)?

I am doing my school project by using Django to create a task management web application. My responsibilities are to create 'issue tracker', something like 'StackOverflow', but I am still at the very early stage of it. So I used crispy form to let the user create their own new issues. Since we use 'project_id' and 'issue_id' as parameters to direct users to different pages, so I encountered this problem, users have to manually choose 'project' when they create a new issue. I do not know how to put the issue which created by the user under right project without having to choose 'project' manually.
form.py
from django import forms
from .models import Comment,Issue
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('body',)
class IssueForm(forms.ModelForm):
class Meta:
model = Issue
fields = ('title','content','project','status')
class NewIssueForm(forms.ModelForm):
class Meta:
model = Issue
fields = ('title','content','project','status')
new_issue.html
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<h1>Add New Issue </h1>
<form method="POST" class="Issue-form">{% csrf_token %}
{{form|crispy}}
<button type="submit" class="btn btn-success">Submit</button>
</form>
{% endblock %}
models.py
class Issue(models.Model):
STATUS_CHOICES = (
('draft', 'Draft'),
('published', 'Published'),
)
project = models.ForeignKey(Project,on_delete=models.CASCADE)
title = models.CharField(max_length=250)
slug = models.SlugField(max_length=250)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
published = models.DateTimeField(default=timezone.now)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
status = models.CharField(max_length=9, choices=STATUS_CHOICES, default='draft')
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
super(Issue, self).save(*args, **kwargs)
def get_absolute_url(self):
return reverse('project:issue_tracker:issue_detail', kwargs={'project_id':self.project_id, 'issue_id':self.id})
def __str__(self):
return self.title
urls.py
urlpatterns =[
path('',views.list_of_issue,name='list_of_issue'),
path('<int:issue_id>/',views.issue_detail,name='issue_detail'),
path('<int:issue_id>/comment',views.add_comment,name='add_comment'),
path('new_issue/',views.new_issue,name='new_issue'),
path('<int:issue_id>/edit_issue/',views.edit_issue,name='edit_issue'),
path('<int:issue_id>/delete_issue/',views.delete_issue,name='delete_issue'),
path('<int:issue_id>/delete', TemplateView.as_view(template_name="issue_tracker/issue/nice_delete.html"), name='success_deletion'),
]
You can set an initial value for the project field in the issue form. An explanation of how that can be done can be found here.
Since you are using project_id and issue_id as parameters, something similar to the following will solve your problem (I guess):
def new_issue(request, project_id, issue_id):
.
.
form = IssueForm(initial={'project': project_id})
.
.

Categories

Resources