Django URL regex complication - python

I am having trouble defining the URLs for my django app.
I want it so that when I type,
http://example.com/post/3 :: I can read a post
http://example.com/post/3/edit :: I can edit a post
At the moment, I defined it like the following:
url(r'^post/(?P<id>\d+)/edit/$',
'board.views.edit_post',
name='edit_post'),
url(r'^post/(?P<id>\d+)',
'board.views.view_post',
name='view_post'),
However this does not seem to work because when I click on my "edit" link which links to
"/post/{{ post.id }}/edit"
I get the intended url in my address bar but am not taken to the edit view...
===================================
--EDIT--
#login_required
def edit_post(request, id):
if id:
post = get_object_or_404(Post, id=id)
if post.owner_user != request.user:
return HttpResponseForbidden()
# else:
# post = Post(owner_user=request.user)
if request.POST:
form = PostEditForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect('')
else:
form = PostEditForm()
return (request,'edit_post.html', {
'form': form})
And
def view_post(request, id):
#context = RequestContext(request)
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
form = form.save(commit=False)
form.owner = request.user
form.parent_post = get_object_or_404(Post, id=id)
form.comment_type = 0
form.save()
return HttpResponseRedirect('')
else:
form = CommentForm()
return render(request,'view_post.html', {
#'comment' : get_object_or_404(Comment, parent_post = id),
'comment': Comment.objects.filter(parent_post=id),
'form': form,
'post': get_object_or_404(Post, id=id)
})

Well, I'll recommend you begin using Class Based Views, besides, why not. Can make your life easier, most times, whiles you write less
class UpdatePost(UpdateView):
model = Post # assuming you've made from your_app.models import Post call already
fields = ['title', 'content', 'pub_date'] # for demonstration purposes
template_name = 'post_form.html'
#method_decorator(login_required) #Use login required on your dispatch method
def dispatch(self, request, *args, **kwargs):
pulled = Post.objects.get(id=kwargs['id'])
if pulled.created_by == request.user: #just to verify person making edit is rightly disposed to do so
return super(UpdatePost, self).dispatch(request, *args, **kwargs)
raise PermissionDenied
Sweetly intercept the id in your url like so
url(r'^app/edit/(?P<id>\d+)/$', views.UpdatePost.as_view(), name='edit'),
That's how I do mine. I pulled this from my working project github.com/seanmavley/menpha.git, but haven't tested this tweaked version. But It should work.

Related

Multi select checkbox doesnt work creating object in django

I have a app where people can declare things, within a decla they can say who was present so they have to pay, only its not working. The edit function works but the fileDecla doesn't.
The part that doensn't work is the present people. When i print the people present (via print(request.POST))before i save the decla it gives all the people selected but then it doesnt save them, and when i print(decla.present) i get --> None.(it should be all the people present.
Does someone know a solution to this?
models.py
class Decla(models.Model):
owner = models.ForeignKey(Lid, on_delete=models.CASCADE)
event = models.ForeignKey(Event, on_delete=models.SET_NULL, null=True, blank=True)
content = models.TextField(max_length=50)
total = models.FloatField()
present = models.ManyToManyField(Lid, related_name="present_leden")
receipt = models.ImageField(
upload_to="declas/", null=True, blank=True
) ## this will need to be put back to nothing when it ends
verwerkt = models.BooleanField(default=False)
views.py
#login_required(login_url="login")
def fileDecla(request):
form = DeclaForm()
if request.method == "POST":
print(1, request.POST)
form = DeclaForm(request.POST, request.FILES)
if form.is_valid():
# print(form)
decla = form.save(commit=False)
decla.owner = request.user.lid
# i tried this line bellow but it didnt work
# decla.present.set(request.POST["present"])
decla.save()
messages.info(request, "Decla was created")
return redirect("agenda")
context = {
"form": form,
"stand": Stand.objects.get(owner_id=request.user.lid.id).amount,
}
return render(request, "finance/decla_form.html", context)
#login_required(login_url="login")
def editDecla(request, pk):
decla = Decla.objects.get(id=pk)
form = DeclaForm(instance=decla)
if request.method == "POST":
print(request.POST)
form = DeclaForm(request.POST, request.FILES, instance=decla)
if form.is_valid():
decla = form.save()
messages.info(request, "Decla was edited")
return redirect(request.GET["next"] if "next" in request.GET else "agenda")
context = {
"form": form,
"stand": Stand.objects.get(owner_id=request.user.lid.id).amount,
}
return render(request, "finance/decla_form.html", context)
forms.py
from django import forms
from django.forms import ModelForm
from django.forms.widgets import NumberInput, CheckboxSelectMultiple
from .models import Decla
class DeclaForm(ModelForm):
class Meta:
model = Decla
fields = "__all__"
exclude = ["owner", "id"]
widgets = {
"present": CheckboxSelectMultiple(),
}
def __init__(self, *args, **kwargs):
super(DeclaForm, self).__init__(*args, **kwargs)
for name, field in self.fields.items():
if not name in ["verwerkt", "present"]:
field.widget.attrs.update({"class": "input"})
# also tried this
# elif name == "present":
# field.widget.attrs.update({"class": "CheckboxSelectMultiple"})
else:
field.widget.attrs.update({"class": "checkbox"})
Here is something from the docs:
Another side effect of using commit=False is seen when your model has
a many-to-many relation with another model. If your model has a
many-to-many relation and you specify commit=False when you save a
form, Django cannot immediately save the form data for the
many-to-many relation. This is because it isn’t possible to save
many-to-many data for an instance until the instance exists in the
database.
To work around this problem, every time you save a form using
commit=False, Django adds a save_m2m() method to your ModelForm
subclass. After you’ve manually saved the instance produced by the
form, you can invoke save_m2m() to save the many-to-many form data.
According to this, adding save_m2m() after calling decla.save() would resolve your issue:
#login_required(login_url="login")
def fileDecla(request):
form = DeclaForm()
if request.method == "POST":
print(1, request.POST)
form = DeclaForm(request.POST, request.FILES)
if form.is_valid():
# print(form)
decla = form.save(commit=False)
decla.owner = request.user.lid
# i tried this line bellow but it didnt work
# decla.present.set(request.POST["present"])
decla.save()
form.save_m2m()
messages.info(request, "Decla was created")
return redirect("agenda")
context = {
"form": form,
"stand": Stand.objects.get(owner_id=request.user.lid.id).amount,
}
return render(request, "finance/decla_form.html", context)
However, this seems like a messy solution. See this antipattern for more info.
My suggestion is to do this:
#login_required(login_url="login")
def fileDecla(request):
form = DeclaForm()
if request.method == "POST":
print(1, request.POST)
form = DeclaForm(request.POST, request.FILES)
if form.is_valid():
# This seems like a much cleaner solution and it should resolve your problem
form.instance.owner = request.user.lid
decla = form.save()
messages.info(request, "Decla was created")
return redirect("agenda")
context = {
"form": form,
"stand": Stand.objects.get(owner_id=request.user.lid.id).amount,
}
return render(request, "finance/decla_form.html", context)
Disclaimer: the code is untested. Let me know if you have any bugs.

Django unclear 404

EDIT : Found the solution myself !
This is what I did. I'm pretty sure it's not a best practice, but it worked for me.
class CommentCreateView(CreateView):
def get(self, request, *args, **kwargs):
context = {'form': CommentForm()}
return render(request, 'news/add_comment_to_article.html', context)
def post(self, request, *args, **kwargs):
form = CommentForm(request.POST)
if form.is_valid():
article = get_object_or_404(Article, pk=kwargs.get('pk'))
print(article)
comment = form.save(commit=False)
comment.post = article
comment.save()
return HttpResponseRedirect(reverse('news:article', kwargs={'article_id': article.pk}))
I have a question, after converting my view (included below) from function based view to class based view I keep getting an error (page not found - 404) after trying to submit a comment on an article. Why is that ?
the view now :
class CommentCreateView(RedirectView):
model = Comment
form_class = CommentForm
template_name = 'news/add_comment_to_article.html'
def form_valid(self, *args, **kwargs):
article = get_object_or_404(Article, pk=kwargs.get('pk'))
comment = form.save(commit=False)
comment.post = article
comment.save()
return HttpResponseRedirect(reverse('news:article', kwargs={'article_id': article.pk}))
the same view how it used to be, function based (working) :
def add_comment_to_article(request, pk):
article = get_object_or_404(Article, pk=pk)
if request.method == "POST":
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.post = article
comment.save()
return HttpResponseRedirect(reverse('news:article', kwargs={"article_id": article.pk}))
else:
form = CommentForm()
return render(request, 'news/add_comment_to_article.html', {'form': form})
comment form:
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('author', 'text',)
Use FormView instead of RedirectView. (We can see that RedirectView doesn't have form_valid method.)
Read more about FormView in Django's official documentation.

Use the same TemplateView for create and update

I have this TemplateView:
class DamageEntry(TemplateView):
template_name = "damage/damageadd.html"
def get(self, request):
general = General.objects.get(pk=1)
form = DamageEntryForm()
args = {'form': form,
'general': general,
}
return render(request, self.template_name, args)
def post(self, request):
general = General.objects.get(pk=1)
form = DamageEntryForm(request.POST)
form.non_field_errors()
if form.is_valid():
post = form.save(commit=False)
if self.request.user.is_authenticated():
post.user = request.user
post.userip = get_client_ip(request) # το IP του χρήστη
location = get_cocation(post.lat, post.lng)
post.location = location
post.formatted_address= location.formatted_address
post.entry_date = datetime.datetime.now(tz=timezone.utc)
post.save()
form = DamageEntryForm()
args = {'form': form,
'general': general
}
return http.HttpResponseRedirect('damage/add/')
else:
print('form is not valid')
print(form.errors)
# form = DamageEntryForm()
args = {'form': form,
'general': general
}
return render(request, self.template_name, args)
It works fine for create new record.
I want to use thw same view for update, because of the extra code on Post section.
I use this URL for update:
# /damage/damage/list/1
url(r'damage/list/(?P<pk>[0-9]+)/$', views.DamageEntry.as_view(), name="damage-by-id"),
Can I do this? How can I pass pk for create and update record?

How do I return only the form in the UpdateView class in Django?

I am trying to switch form a functional view that edits and submits a form to class based view, UpdateView. I need to access the form.
Until now I use this view:
#login_required
def edit_sale_view(request, id):
instance = get_object_or_404(Sale, id=id)
form = EditSaleForm(instance=instance)
if request.method == "POST":
form = EditSaleForm(request.POST, instance=instance)
if form.is_valid():
obj = form.save(commit=False)
obj.user = request.user
obj.save()
return JsonResponse({'msg': 'Data saved'})
else:
return JsonResponse({'msg': 'Data not saved'})
return HttpResponse(form.as_p())
The form.as_p() is important because it is used in the form that is displayed in a modal window.
So, I think that I need to find out how to return only the form and not the whole template.
You can access form instance by overriding form_valid method. For example:
class SomeView(UpdateView):
...
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.user = self.request.user
self.object.save()
return HttpResponseRedirect(self.get_success_url()) # or use return JsonResponse({'msg': 'Data saved'})
def get_initial(self):
initial = super(SomeView, self).get_initial()
# do something
return initial()

Possible to make these two django views DRY-er?

I'm currently learning the Python / Django stack by following some training to build a blog.
I currently have two similar views for adding new and editing existing posts (post_new and post_edit) as below:
def post_new(request):
if request.method == "POST":
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.published_date = timezone.now()
post.save()
return redirect("post_detail", pk=post.pk)
else:
form = PostForm()
return render(request, "blog/post_edit.html", {"form": form})
def post_edit(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == "POST":
form = PostForm(request.POST, instance=post)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.published_date = timezone.now()
post.save()
return redirect("post_detail", pk=post.pk)
else:
form = PostForm(instance=post)
return render(request, "blog/post_edit.html", {"form":form})
Although these views do different jobs they share some identical code.
Trying to follow best practice (DRY), is there a sensible way to make such similar views DRYer? Or is it better to leave views of this sort of length in long form to keep them easy to read?
I would personally write it like this:
def post_edit(request, pk=None):
if pk is not None:
post = get_object_or_404(Post, pk=pk)
else:
post = None
if request.method == "POST":
form = PostForm(request.POST, instance=post)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.published_date = timezone.now()
post.save()
return redirect("post_detail", pk=post.pk)
else:
form = PostForm(instance=post)
return render(request, "blog/post_edit.html", {"form":form})
Basically, you pass the default instance value to the ModelForm.
You probably want to use Class-based views for that.
from django.views.generic.edit import CreateView, UpdateView
class PostCreate(CreateView):
model = Post
fields = ['name', ...]
class PostUpdate(UpdateView):
model = Post
fields = ['name', ...]

Categories

Resources