I have a Dictionary view that shows the list of words created by a specific (special) user:
class Dictionary(FilterView):
model = Word
template_name = 'vocab/dictionary.html'
context_object_name = 'dict_list'
paginate_by = 15
filterset_class = WordFilter
strict = False
def get_queryset(self):
qs = self.model.objects.filter(user__username__iexact='special_user')
return qs
def get_object(self):
queryset = qs
pk = self.kwargs.get('pk')
if pk is None:
raise AttributeError('pk expected in url')
return get_object_or_404(queryset, pk=pk)
Now I want any user to be able to come to this page and add any word that they want to, like this:
def custom_create_word(request, object):
if request.method == 'POST':
pass
if request.method =="GET":
from .forms import WordForm
from .models import Word
word = Word.objects.get(pk=object)
user = request.user
target_word = word.target_word
source_word = word.source_word
deck_name = "My Words"
fluency = 0
new_word, created = Word.objects.get_or_create(user=user, target_word=target_word,
source_word=source_word, deck_name=deck_name, fluency=fluency)
return HttpResponseRedirect(reverse('vocab:dict'))
Everything works as expected. But in the template I want the button to look different depending on whether the logged in user already has this word in their own list (which should be judged by if target_word is the same). My template looks like this:
<tr>
{% for word in dict_list %}
<td>{{word.target_word}}</td>
<td>{{word.source_word}}</td>
<td>
{% if user_word %}
<a href="" class="btn btn-success btn-sm" >Added</a>
{% else %}
Add
{% endif %}
</td>
</tr>
{% endfor %}
The way I thought about doing this is to overwrite get_context_data on my Dictionary view so that I can check if the logged in user's target_word is equal to the special user's target_word, and to pass this into context. So my view is:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['filter'] = WordFilter(self.request.GET, queryset=self.get_queryset())
special_user_word = Word.objects.filter(user__username__iexact='special_user', target_word='target_word')
logged_user_word = Word.objects.filter(user=self.request.user, target_word='target_word')
user_word = None
if special_user_word == logged_user_word:
user_word = True
context['user_word'] = user_word
return context
But I get None everywhere... any thoughts?
Obviously they won't be same, because the Word objects are totally different as they are created differently for each user inside custom_create_word. Also, user_word won't work for all the words, you need provide it for each word. You can override the get_queryset method like this(using conditional expression):
from django.db.models import Value, Case, When, BooleanField
class WordListView(...):
...
def get_queryset(self, **kwargs):
queryset = super().get_queryset(**kwargs)
special_user_word = Word.objects.filter(user__username__iexact='special_user', target_word='target_word').values('source_word', flat=True)
return queryset.annotate(
user_word=Case(
When(source_word__in=special_user_word, then=Value(True)),
default=Value(False),
output_field=BoolenField()
)
)
Usage in template:
{% for word in dict_list %}
{% if word.user_word %}
already added
{% else %}
add
{% endif %}
{% endfor %}
Finally got it to work!
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['filter'] = WordFilter(self.request.GET, queryset=self.get_queryset())
special_user_word = Word.objects.filter(user__username__iexact='special_user').values_list('target_word', flat=True)
logged_user_word = Word.objects.filter(user=self.request.user).values_list('target_word', flat=True)
user_word = list(set(special_user_word) & set(logged_user_word))
context['user_word'] = user_word
return context
And in my template:
{% if word.target_word in user_word %}
Related
class ManageStudentView(ListView):
model = Student
template_name = 'student/manage_student.html'
def get_context_data(self, **kwargs):
kwargs = super(ManageStudentView,
self).get_context_data(**kwargs)
kwargs['student'] = User.objects.filter(user_type=STUDENT)
return kwargs
def get_paginate_by(self, queryset):
self.paginate_by = settings.PAGINATION_NUMBER
return self.paginate_by
First make sure that import your models and ListView
class YourViewNameHere(ListView):
paginate_by = 2
model = Your Model name
This limits the number of objects per page and adds a paginator and page_obj to the context. To allow your users to navigate between pages, add links to the next and previous page, in your template like this:
{% for some in page_obj %}
{# Each "some" is a Your model object. #}
{{ some.full_name|upper }}<br>
...
{% endfor %}
I am looking to modify a form from the user and return that different form in django, however I have tried many different ways, all in views.py, including:
Directly modifying it by doing str(form) += "modification"
returning a new form by newform = str(form) + "modification"
creating a different Post in models, but then I realized that wouldn't work because I only want one post
All the above have generated errors such as SyntaxError: can't assign to function call, TypeError: join() argument must be str or bytes, not 'HttpResponseRedirect', AttributeError: 'str' object has no attribute 'save', and another authority error that said I can't modify a form or something like that.
Here is a snippet from views.py:
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ['content']
title = ['title'] #
template_name = 'blog/post_new.html'
success_url = '/'
def form_valid(self, form):
#debugging
tempvar = (str(form).split('required id="id_content">'))[1].split('</textarea></td>')[0] #url
r = requests.get(tempvar)
tree = fromstring(r.content)
title = tree.findtext('.//title')
print(title)
form.instance.author = self.request.user
if "http://" in str(form).lower() or "https://" in str(form).lower():
if tempvar.endswith(' '):
return super().form_valid(form)
elif " http" in tempvar:
return super().form_valid(form)
elif ' ' not in tempvar:
return super().form_valid(form)
else:
return None
models.py:
class Post(models.Model):
content = models.TextField(max_length=1000)
title = models.TextField(max_length=500, default='SOME STRING') #
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
likes= models.IntegerField(default=0)
dislikes= models.IntegerField(default=0)
def __str__(self):
return (self.content[:5], self.title[:5]) #
#property
def number_of_comments(self):
return Comment.objects.filter(post_connected=self).count()
And in home.html, where the post (along with the title and content) is supposed to be shown:
<a
style="color: rgba(255, 255, 255, 0.5) !important;"
href="{% url 'post-detail' post.id %}">
<p class="mb-4">
{{ post.content }}
{{ post.title }} #
</p>
</a>
The original template I'm modifying can be found here.
Thank you so much for your help, I will be very glad to take any advice!!
Ps: I'm using Python 3.7.4
Create a forms.py file inside the app you are talking about, it should look like this:
from django import forms
from . import models
class YourFormName(forms.ModelForm):
class Meta:
model = models.your_model_name
fields = ['field1', 'field2' ,...] # Here you write the fields of your model, this fields will appear on the form where user post data
Then you call that form into your views.py so Django can render it into your template, like this:
def your_view(request, *args, **kwargs):
if request.method == 'POST':
form = forms.YourFormName(request.POST, request.FILES)
if form.is_valid():
instance = form.save(commit=False)
instance.user= request.user
instance.save()
return redirect('template.html') # To redirect if the form is valid
else:
form = forms.YourFormName()
return render(request, "template.html", {'form': form}) # The template if the form is not valid
And the last thing to do is create the template.html:
{% extends 'base.html' %}
{% block content %}
<form action="{% url 'the_url_that_renders_this_template' %}" method='POST' enctype="multipart/form-data">
{% csrf_token %}
{{ form }}
<button type="submit">Submit</button>
</form>
{% endblock content %}
If you want to take the data from DB submitted in that form, you do so with a new function in views.py:
def show_items(request, *args, **kwargs):
data = YourModelName.objects.all()
context = {
"data": data
}
return render(request, "show_items.html", context)
Then in show_items.html:
{% extends 'base.html' %}
{% block content %}
{% for item in data %}
{{item.field1}}
{{item.field2}}
...
{{The items you want to show in that template}}
{% enfor %}
{% endblock content %}
That's what you wanted to do? If not, add a further explanation on what actually you want to do
I want to show one button if a user has already added an object, and another button if he/she hasn't.
In my template I have:
<tr>
{% for word in dict_list %}
<td>{{word.target_word}} </td>
<td>{{word.source_word}}</td>
<td>
Add
{% if user_word %}
<a href="" class="add-word btn btn-success btn-sm" >Added</a>
{% endif %}
</td>
</tr>
{% endfor %}
And in my views:
def custom_create_word(request, object):
if request.method == 'POST':
pass
if request.method =="GET":
from .forms import WordForm
from .models import Word
word = Word.objects.get(pk=object)
user = request.user
target_word = word.target_word
source_word = word.source_word
deck_name = "My Words"
fluency = 0
new_word, created = Word.objects.get_or_create(user=user, target_word=target_word,
source_word=source_word, deck_name=deck_name, fluency=fluency)
return HttpResponseRedirect(reverse('vocab:dict'))
def get_context_data(self,**kwargs):
context = super(custom_create_word, self).get_context_data(**kwargs)
if Word.objects.filter(target_word=target_word, user=user).exists():
user_word == True
context['user_word'] = user_word
return context
I don't get any errors, but I don't get the desired result either. Am I going about it the wrong way?
Updated template:
<tbody>
<tr>
{% for word in dict_list %}
<td>{{word.target_word}}</td>
<td>{{word.source_word}}</td>
<td>
{% if user_word %}
<a href="" class="btn btn-success btn-sm" >Added</a>
{% else %}
Add
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
Updated views:
class Dictionary(FilterView):
model = Word
template_name = 'vocab/dictionary.html'
context_object_name = 'dict_list'
paginate_by = 15
filterset_class = WordFilter
strict = False
def get_queryset(self):
qs = self.model.objects.filter(user__username__iexact='special_user')
return qs
def get_object(self):
queryset = qs
pk = self.kwargs.get('pk')
if pk is None:
raise AttributeError('pk expected in url')
return get_object_or_404(queryset, pk=pk)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['filter'] = WordFilter(self.request.GET, queryset=self.get_queryset())
word = Word.objects.get(pk=self.kwargs.get('pk'))
target_word = word.target_word
context['user_word'] = Word.objects.filter(target_word=target_word, user=self.request.user).exists()
return context
Update: If I hard code the pk in the above, I get the desired result, i.e. Add if word doesn't exist, and Added if it does. But the pk=self.kwargs.get('pk') doesn't work, I get DoesNotExist error - what should it be? How can I get access to each object's pk in the ListView?
urls.py:
app_name='vocab'
urlpatterns = [
path("index/",views.VocabHome.as_view(),name='index'),
path("list/", views.WordList.as_view(), name="list"),
path("create/", views.CreateWord.as_view(), name="create"),
re_path(r"by/(?P<username>[-\w]+)/(?P<pk>\d+)/$",views.WordDetail.as_view(),name="detail"),
re_path(r"delete/(?P<pk>\d+)/$",views.WordDelete.as_view(),name="delete"),
re_path(r"edit/(?P<pk>\d+)/$",views.WordUpdate.as_view(),name="edit"),
path('dictionary/', views.Dictionary.as_view(),name='dict'),
path("<int:object>/",views.custom_create_word,name="add-custom"),
]
I've also now tried the following:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['filter'] = WordFilter(self.request.GET, queryset=self.get_queryset())
special_user_word = Word.objects.filter(user__username__iexact='special_user', target_word='target_word')
logged_user_word = Word.objects.filter(user=self.request.user, target_word='target_word')
user_word = None
if special_user_word == logged_user_word:
user_word = True
context['user_word'] = user_word
return context
But I get None everywhere... any thoughts?
Can you share the url's file, in the updated view i don't see how the function is getting called and which function is called.
It's user_word = True, not user_word == True.
Also in your template you should use the else clause otherwise when user_word is True you'll have 2 buttons.
Finally, def get_context_data(self,**kwargs): will not be executed because you are using a function as a view. get_context_data is for generic class based views. And even if it were executed, which I guess it's not, target_word that you use in it is not defined.
Update for you new problem
replace
word = Word.objects.get(pk=self.kwargs.get('pk'))
with
word = Word.objects.get(pk=kwargs.get('pk'))
this is what I'm trying to do: users can create community(or post) and I want in some where in my template, to display the community the user created.
So in models.py I have
class Category(models.Model):
name = models.CharField(max_length=128, unique=True)
description = models.TextField()
author = models.ForeignKey(settings.AUTH_USER_MODEL)
and in my views.py
#login_required
def add_category(request):
if not request.user.is_superuser and Category.objects.filter(author=request.user).exists():
return render(request,'main/category_already_exists.html')
if request.method == 'POST':
category = Category(author=request.user)
form = CategoryForm(request.POST, request.FILES, instance=category)
if form.is_valid():
form.save(commit=True)
return redirect('category', category_name_url=category.name)
else:
form = CategoryForm()
context = {
"form":form
}
return render(request, 'main/add_category.html',context)
and this is my simplified category view
def category(request, category_name_url):
category_name = decode_url(category_name_url)
category = Category.objects.get(name=category_name)
context = {
"category":category,
}
return render(request, "main/category.html", context)
so I tried this in my template with thinking it would work for sure
{% if user.is_authenticated == category.author %}
<h1>hello</h1>
{% else %}
<h1>nah</h1>
{% endif %}
but when it should print hello it prints nah, what's my fault here?
{% if user.is_authenticated == category.author %}
is not a valid check. user.is_authenticated returns a boolean (True/False), and category.author is a user object. You are checking for equality incorrectly.
Try this instead:
{% if user.is_authenticated and user == category.author %}
since category.author is a User instance
My aim is to use Two models in One template. I have tried various ways around this and have had no success. Originally I had 2 views, 2 models and Two forms. After searching I found people using inlineformsets. So I dropped One of the Views and set-up the inlineformset.
This is currently where I am up to and seem to be hitting a wall.
The template renders to the browser and the 'object_list' part displays the database content as desired and the 'form' part renders the form and validates/saves the data correctly. The issue is with the 'formset'. No fields are rendered (I would expect to see a dropdown as the field is a foreignkey) and when the 'submit' button is pressed I get:
AttributeError at /settings/
'NoneType' object has no attribute 'save'
Any help in finding the error or pointers on alternative solutions would be greatly appreciated.
The Code:
models.py
from django.db import models
class RevisionSettings(models.Model):
global_revision_type = models.CharField(max_length = 5, unique=True, blank = True)
global_revision_description = models.CharField(max_length = 300, unique=True, blank = True)
class Meta:
ordering = ["global_revision_type"]
def __unicode__(self):
return u'%s %s' % (self.global_revision_type, self.global_revision_description)
class RevisionDefaultType(models.Model):
defaultrevisiontype = models.ForeignKey(RevisionSettings)
class Meta:
ordering = ["defaultrevisiontype"]
def __unicode__(self):
return unicode(self.defaultrevisiontype)
views.py
class RevisionSettingsView(CreateView):
template_name = 'settings/revisionsettings_view.html'
model = RevisionSettings
form_class = SettingsForm
success_url = reverse_lazy('globalsettings')
success_message = 'Successfully added your new revision type'
def get(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
formset = SettingsFormSet(instance = RevisionSettings)
return self.render_to_response(
self.get_context_data(form=form,
formset=formset))
def post(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
formset = SettingsFormSet(self.request.POST)
if 'rev_settings_form_1' in self.request.POST:
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
elif 'rev_settings_form_2' in self.request.POST:
if formset.is_valid():
return self.formset_valid(formset)
else:
return self.form_invalid(formset)
def form_valid(self, form):
self.object = form.save()
self.object.save()
return HttpResponseRedirect(self.get_success_url())
def formset_valid(self, formset):
self.object.save()
formset.instance = self.object
formset.save()
return HttpResponseRedirect(self.get_success_url())
def form_invalid(self, form, formset):
return self.render_to_response(self.get_context_data(form=form,formset=formset))
def get_context_data(self, **kwargs):
kwargs['object_list'] = RevisionSettings.objects.order_by('global_revision_type')
return super(RevisionSettingsView, self).get_context_data(**kwargs)
forms.py
from django import forms
from django.forms.models import inlineformset_factory
from .models import RevisionSettings, RevisionDefaultType
class SettingsForm(forms.ModelForm):
class Meta:
model = RevisionSettings
class DefaultSettingsForm(forms.ModelForm):
class Meta:
model = RevisionDefaultType
SettingsFormSet = inlineformset_factory(RevisionSettings, RevisionDefaultType)
revisionsettings_view.html
(I have removed most of the HTML styling to keep the information to the point)
{% extends 'base_private.html' %}
{% block content %}
{% for object in object_list %}
<tr>
<td align="center">{{ object.global_revision_type }}</td>
<td align="center">{{ object.global_revision_description }}</td>
<td align="center"><span class="glyphicon glyphicon-remove-circle"></span></td>
</tr>
{% endfor %}
<form action = '{{ action }}' method = 'POST' class="form-horizontal" role="form">
{% csrf_token %}
<tr>
<td align="center">
{{ formset.management_form }}
{% for form in formset %}
{{ form.id }}
{{ form.defaultrevisiontype.label_tag }}
{{ form.defaultrevisiontype }}
{% endfor %}
</td>
</tr>
<span class="input-group-addon">
<input type = 'submit' name = 'rev_settings_form_2' value = 'Update Default Revision Type' class = 'btn btn-success'>
</span>
<td align="center">{{ form.global_revision_type }}{{ form.global_revision_type.errors }}</td>
<td align="center">{{ form.global_revision_description }}{{ form.global_revision_description.errors }}</td>
</tr>
<span class="input-group-addon">
<input type = 'submit' name = 'rev_settings_form_1' value = 'Add Revision Type' class = 'btn btn-success'>
</span>
</form>
{% endblock %}
Formsets are overkill for two forms. This is actually not too hard but poorly documented. You can make both forms the same form type, just give a prefix.
def parent_apply(request):
if request.method == 'POST':
parent_form = SignupForm(request.POST, prefix="parent")
student_form = StudentApplyForm(request.POST, prefix="student")
if parent_form.is_valid() and student_form.is_valid():
parent = parent_form.save()
student = student_form.save(parent)
else: messages.error(request, "Please correct the errors marked in red.")
else:
parent_form = SignupForm(prefix="parent")
student_form = StudentApplyForm(prefix="student")
return render_to_response('template_path/forms.html', { 'parent_form':parent_form, 'student_form':student_form }, context_instance=RequestContext(request))
The forms are just regular Django forms, no special settings required. You can change the order on which they validate and save one even if the other did not validate if you choose.
In your HTML Template, wrap both forms in the same tag and they will submit at the same time. If you want your forms to go to different view functions, specify two different elements.
Thanks for all the help. The pointers really helped me come to this solution. The main change was to 'def get' as shown below. I dropped the formset and passed the forms this way.
def get(self, request, *args, **kwargs):
form = self.settings_form_class
formset = self.default_form_class
return self.render_to_response(self.get_context_data(form = form, formset = formset))
I was unaware this was possible! Thanks again.