I am creating a search application with Django.
I made an article model and a Feedback model that records the rating of articles.
After entering search box and displaying the search results, click one of the results then goes to the detail screen.
After selecting feedback on the detail screen and pressing the submit button, I want to save a search query to the feedback model.
I think that solution is to add a query in the URL like portal/search/?=query and read it, but I don't know how to code it. Also, could you teach me if there is an implementation method other than reading query in the URL?
Also, when I go back from the detail screen, I want to display the previous search results too.
Please comment if you have any questions.
Forgive for my poor English.
models.py
from django.db import models
from django.urls import reverse
from taggit.managers import TaggableManager
class KnowHow(models.Model):
BASIC_TAGS =(
('1','one'),
('2','two'),
('3','three'),
('4','four'),
('5','five'),
('6','six'),
)
CATEGORY =(
('1','Type2'),
('2','Type1'),
)
author = models.ForeignKey('auth.User',on_delete=models.CASCADE)
category = models.CharField(max_length=1,choices=CATEGORY,default='1')
title = models.CharField(max_length=200)
text = models.TextField(blank=True,default=' ')
# delault=' ':import system will give a error if text column is null
file = models.FileField(blank=True,upload_to='explicit_knowhows')
basic_tag = models.CharField(max_length=1,choices=BASIC_TAGS,default='1')
free_tags = TaggableManager(blank=True)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('portal:index')
class Feedback(models.Model):
EFFECT =(
('1','great'),
('2','maybe good'),
('3','bad'),
)
NOVEL =(
('1','I didn't know that'),
('2','I know, but I forgot'),
('3','I know this.'),
)
kh = models.ForeignKey(KnowHow, on_delete=models.PROTECT)
user = models.ForeignKey('auth.User',on_delete=models.CASCADE)
query = models.TextField(blank=True)
time = models.DateTimeField(auto_now_add=True)
efficacy = models.CharField(max_length=1,choices=EFFECT,default='1')
novelty = models.CharField(max_length=1,choices=NOVEL,default='1')
def __str__(self):
return self.time.strftime("%Y/%m/%d %H:%M:%S")
views.py
from django.urls import reverse, reverse_lazy
from django.http import HttpResponse
from django.views import generic
from django.views.generic.edit import ModelFormMixin
from django.shortcuts import redirect,get_object_or_404
from django.core.exceptions import PermissionDenied
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.decorators import login_required
from .models import KnowHow
from taggit.models import Tag
from .forms import SearchForm,FeedbackForm
from django.db.models import Q
"""
Django Auth
The LoginRequired mixin
https://docs.djangoproject.com/en/2.0/topics/auth/default/#the-loginrequired-mixin
The login_required decorator
https://docs.djangoproject.com/en/2.0/topics/auth/default/#the-login-required-decorator
#login_required
"""
class IndexView(LoginRequiredMixin,generic.list.ListView):
model = KnowHow
#paginate_by = 5
ordering = ['-title']
# template_name = 'portal/KnowHow_list.html'
class DetailView(ModelFormMixin,LoginRequiredMixin,generic.detail.DetailView):
# from https://torina.top/detail/337/
model = KnowHow
form_class = FeedbackForm
template_name = 'portal/KnowHow_detail.html'
def form_valid(self, form):
kh_pk = self.kwargs['pk']
Feedback = form.save(commit=False)
Feedback.kh = get_object_or_404(KnowHow, pk=kh_pk)
Feedback.query=""
Feedback.user=self.request.user
Feedback.save()
return redirect('portal:search')
def post(self, request, *args, **kwargs):
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
self.object = self.get_object()
return self.form_invalid(form)
class CreateView(LoginRequiredMixin, generic.edit.CreateView): # The LoginRequired mixin
model = KnowHow
fields = ['category','title','text','file','basic_tag','free_tags']
#template_name = 'portal/KnowHow_form.html'
def form_valid(self, form):
# This method is called when valid form data has been posted.
# It should return an HttpResponse.
# https://docs.djangoproject.com/en/2.0/topics/class-based-views/generic-editing/#models-and-request-user
form.instance.author = self.request.user
return super(CreateView, self).form_valid(form)
class UpdateView(LoginRequiredMixin, generic.edit.UpdateView): # The LoginRequired mixin
model = KnowHow
fields = ['category','title','text','file','basic_tag','free_tags']
#template_name = 'portal/KnowHow_form.html'
class DeleteView(LoginRequiredMixin, generic.edit.DeleteView): # The LoginRequired mixin
model = KnowHow
success_url = reverse_lazy('portal:index')
def delete(self, request, *args, **kwargs):
result = super().delete(request, *args, **kwargs)
Tag.objects.filter(knowhow=None).delete()
return result
#template_name = 'portal/KnowHow_confirm_delete.html'
class SearchIndexView(LoginRequiredMixin, generic.ListView):
template_name="search/search_index.html"
model = KnowHow
def post(self, request, *args, **kwargs):
form_value = [
self.request.POST.get('basic_tag', None),
self.request.POST.get('free_tags', None),
]
request.session['form_value'] = form_value
self.request.GET = self.request.GET.copy()
self.request.GET.clear()
return self.get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
basic_tag = ''
free_tags = ''
if 'form_value' in self.request.session:
form_value = self.request.session['form_value']
basic_tag = form_value[0]
free_tags = form_value[1]
default_data = {'basic_tag': basic_tag,
'free_tags': free_tags,
}
test_form = SearchForm(initial=default_data)
context['test_form'] = test_form
return context
def get_queryset(self):
if 'form_value' in self.request.session:
form_value = self.request.session['form_value']
basic_tag = form_value[0]
free_tags = form_value[1]
condition_basic_tag = Q()
condition_free_tags = Q()
if len(basic_tag) != 0 and basic_tag[0]:
condition_basic_tag = Q(basic_tag=basic_tag)
if len(free_tags) != 0 and free_tags[0]:
condition_free_tags = Q(free_tags__name__in=free_tags)
return KnowHow.objects.filter(condition_basic_tag & condition_free_tags).distinct()
else:
return KnowHow.objects.none()
#login_required
def help(request):
return HttpResponse("Member Only Help Page")
urls.py
from django.urls import path
from . import views
# set the application namespace
# https://docs.djangoproject.com/en/2.0/intro/tutorial03/
app_name = 'portal'
urlpatterns = [
# ex: /
path('', views.IndexView.as_view(), name='index'),
# ex: /KnowHow/create/
path('KnowHow/create/', views.CreateView.as_view(), name='create'),
# ex: /KnowHow/1/
path('KnowHow/<int:pk>/detail/', views.DetailView.as_view(), name='detail'),
# ex: /KnowHow/1/update/
path('KnowHow/<int:pk>/update/', views.UpdateView.as_view(), name='update'),
# ex: /KnowHow/1/delete
path('KnowHow/<int:pk>/delete/', views.DeleteView.as_view(), name='delete'),
# ex: /KnowHow/help/
path('KnowHow/help/', views.help, name='help'),
path('search/',views.SearchIndexView.as_view(), name='search')
]
There are several solutions for your problem.
First one is the exact solution you mentioned yourself. using a query string parameter like ?q= for KnowHow details view.
Using a SearchLog model and using that model's identifier. When someone hits the /search/ endpoint, you create a new SearchLog and pass the pk for this record to your front. Basically it would be just like ?q= option. instead you can use ?search_id= to bind the feedback to an specific SearchLog
Use user sessions. Bind the searched query to user's session and when they want to create a new Feedback use the query in their session.
For the first two options, you just need to create your urls for the detail links properly (in your search result page). In your template, do something like below:
# You are probably doing something like this
{% for r in results %}
{{r.name}}
{% endfor %}
# You should do this instead
{% for r in results %}
{{r.name}}
{% endfor %}
You can either pass the current_query in your context when rendering the template, or use javascript to get that value from browser's location / query string.
I changed get_context_data function in SearchIndexView to this:
in the last line before return add these two lines
context['basic_tag'] = basic_tag
context['free_tags'] = free_tags
And I changed html too.
{{ KnowHow.title }}
Thanks, #n1ma
Related
Ok so I am trying to include the corrosponding Comment in my createAnswer View currently the url of the createAnswer page includes the pk ok the right comment so i need to get the comment by the id in the url.
My generic CreateView looks like this:
class createAnswer(CreateView):
model = Answer
fields = ['content']
def getComment(self, request):
???
comment = getComment()
def get_success_url(self):
this_path = self.request.get_full_path()
path_list = this_path.split('/')
def get_comment_id(self):
for i in range(len(path_list)):
if path_list[i].isdigit():
return path_list[i]
return '/blogcontact/comment/'+ get_comment_id(self)
def form_valid(self,form):
this_path = self.request.get_full_path()
path_list = this_path.split('/')
def get_comment_id(self):
for i in range(len(path_list)):
if path_list[i].isdigit():
return path_list[i]
form.instance.author = self.request.user
form.instance.comment = Comment.objects.get(id=get_comment_id(self))
return super().form_valid(form)
My Urls.py looks like this:
from django.urls import path
from . import views
from .views import createAnswer
urlpatterns = [
path('contact/comment/<int:pk>/newanswer', createAnswer.as_view(),
name='answer-create')
]<br>
I would like to save the Comment object in a variable so i Can use it in the html template like this {{comment}}
I think you you are confusing the function views and the Class Based Views (CBV), and you never import a request, it is just a parameter your views receive.
In a function view you do the following:
def my_view(request):
if request.method == 'POST':
# do some stuff
For CBV each method is a function:
from django.views.generic.edit import CreateView
class MyView(CreateView):
model = Answer
fields = ['content']
def get(self, request):
# do some stuff
def post(self, request):
# do some stuff
EDIT: To access the url parameters in class based views use self.kwargs, so you would access the comment pk by doing self.kwargs['pk']. Now you just need to get the comment and add it to the context data:
class CreateAnswer(CreateView):
model = Answer
fields = ['content']
def get_context_data(self, **kwargs):
kwargs['comment'] = Comment.objects.get(pk=self.kwargs['pk'])
return super().get_context_data(**kwargs)
def form_valid(self, form):
# do some stuff
Python :3.6.2 Django : 1.11.4
We are trying to use foreignkey across apps. Address is consumed by both customer and agent. We are also using inline formset. Form showing up correct. Please see below for the picture.
Create Agent Screen
However, when you save it, it saves just the just last of address. Also please note that in edit, a blank line is added which I think should not added by default. Please see below for the pic.
Edit Agent screen
Folder structure
apps (Folder)
agent (app)
models.py
views.py
customer (app)
models.py
views.py
sharedmodels(app)
models.py
forms.py
agent/views.py
from django.shortcuts import render
# Create your views here.
from django.db import transaction
from django.urls import reverse_lazy
from django.views.generic import CreateView, UpdateView, ListView
from apps.agent.models import Agent
from apps.sharedmodels.forms import AgentAddressFormSet
from multiApps.logger import log
class AgentList(ListView):
model = Agent
class AgentCreate(CreateView):
model = Agent
fields = ['first_name']
#log.info(fields)
class AgentAddrLocArefCreate(CreateView):
model = Agent
fields = ['first_name']
success_url = reverse_lazy('agent-list')
def get_context_data(self, **kwargs):
data = super(AgentAddrLocArefCreate, self).get_context_data(**kwargs)
log.info('i m here 1')
if self.request.POST:
log.info('i m here 2')
#data['location'] = LocationFormSet(self.request.POST)
data['address'] = AgentAddressFormSet(self.request.POST)
#data['agentreferal'] = AgentReferalFormSet(self.request.POST)
else:
#data['location'] = LocationFormSet()
data['address'] = AgentAddressFormSet()
#data['agentreferal'] = AgentReferalFormSet()
return data
def form_valid(self, form):
context = self.get_context_data()
#location = context['location']
agentaddress = context['address']
log.info('i m here 3')
#agentreferal = context['agentreferal']
with transaction.atomic():
self.object = form.save()
#if location.is_valid() and address.is_valid() and agentreferal.is_valid():
if agentaddress.is_valid():
#location.instance = self.object
agentaddress.instance = self.object
#agentreferal.instance = self.object
#location.save()
agentaddress.save()
#agentreferal.save()
return super(AgentAddrLocArefCreate, self).form_valid(form)
class AgentAddrLocArefUpdate(UpdateView):
model = Agent
fields = ['first_name']
success_url = reverse_lazy('agent-list')
# log.info('i m here 4')
def get_context_data(self, **kwargs):
data = super(AgentAddrLocArefUpdate, self).get_context_data(**kwargs)
log.info(data)
if self.request.POST:
log.info('i m here 5')
#data['location'] = LocationFormSet(self.request.POST, instance=self.object)
data['address'] = AgentAddressFormSet(self.request.POST, instance=self.object)
#data['agentreferal'] = AgentReferalFormSet(self.request.POST, instance=self.object)
else:
#data['location'] = LocationFormSet(instance=self.object)
data['address'] = AgentAddressFormSet(instance=self.object)
#data['agentreferal'] = AgentReferalFormSet(instance=self.object)
return data
def form_valid(self, form):
context = self.get_context_data()
#location = context['location']
agentaddress = context['address']
log.info('i m here 6')
#agentreferal = context['agentreferal']
with transaction.atomic():
self.object = form.save()
#if location.is_valid() and \
if agentaddress.is_valid():
#location.instance = self.object
agentaddress.instance = self.object
#agentreferal.instance = self.object
#location.save()
agentaddress.save()
#agentreferal.save()
return super(AgentAddrLocArefUpdate, self).form_valid(form)
def brokenview(request):
# first, intentionally log something
log.info('This is a manually logged INFO string.')
log.debug('This is a manually logged DEBUG string.')
# then have the view raise an exception (e.g. something went wrong)
raise Exception('This is an exception raised in a view.')
# return HttpResponse('hello')
sharedmodels/forms.py
from django.forms import ModelForm, inlineformset_factory
from apps.agent.models import Agent
from apps.customer.models import Customer
from .models import *
class AgentAddressForm(ModelForm):
class Meta:
model = Address
exclude = ('customer',)
class AddressForm1(ModelForm):
class Meta:
model = Address
exclude = ('agent',)
AgentAddressFormSet = inlineformset_factory(Agent, Address, form=AgentAddressForm, extra=1)
AddressFormSet1 = inlineformset_factory(Customer, Address, form=AddressForm1, extra=1)
#LocationFormSet = inlineformset_factory(Agent, Location, form=LocationForm, extra=1)
***One other point: We are successful in doing this within one app.
Any immediate help on this really appreciated.
I'm tryinig to get haystack working with a class-based generic view according to the documentation here. I can get results from a SearchQuerySet in the shell, so the models are being indexed. But I can't get the view to return a result on the page.
The main reason for using the generic view is that I want to extend later with more SQS logic.
I'm probably missing something obvious...
views.py :
from haystack.query import SearchQuerySet
from haystack.generic_views import SearchView
from .forms import ProviderSearchForm
from .models import Provider
class ProviderSearchView(SearchView):
template_name = 'search/provider_search.html'
form_class = ProviderSearchForm
def get_context_data(self, *args, **kwargs):
""" Extends context to include data for services."""
context = super(ProviderSearchView, self).get_context_data(*args, **kwargs)
context['body_attr'] = 'id="provider-search"'
return context
def get_queryset(self):
queryset = super(ProviderSearchView, self).get_queryset()
return queryset.filter(is_active=True)
search_indexes.py:
from haystack import indexes
from .models import Provider
class ProviderIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
title = indexes.CharField(model_attr='name')
created = indexes.DateTimeField(model_attr='created')
def get_model(self):
return Provider
def index_queryset(self, using=None):
"Used when the entire index for model is updated."
return self.get_model().objects.all()
forms.py
from django import forms
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Field, Submit
from crispy_forms.bootstrap import FieldWithButtons
from haystack.forms import SearchForm
from .models import Provider
class ProviderSearchForm(SearchForm):
""" Override the form with crispy styles """
models = [ Provider ]
def __init__(self, *args, **kwargs):
super(ProviderSearchForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.disable_csrf = True
self.helper.form_tag = False
self.helper.form_show_labels = False
self.helper.layout = Layout (
FieldWithButtons(
Field('q', css_class='form-control input-lg', placeholder="Search providers..."),
Submit('','Search', css_class='btn btn-lg btn-primary'))
)
def get_models(self):
return self.models
def search(self):
sqs = super(ProviderSearchForm, self).search().models(*self.get_models())
return sqs
def no_query_found(self):
return self.searchqueryset.all()
The problem was that my page template was using the wrong variable in the for loop.
The documentation suggests:
for result in page_object.object_list
It should be:
for result in page_obj.object_list
note the template variable is page_obj.
See issue post on GitHub
I am following the tutorial of Django REST Framework and when I try to curl http://127.0.0.1:8000/snippets, I get that error. I am new to Python/the Framework and Django, so I don't have a clue where to look at.
The code seems to be pretty fine, since I have double checked on github. Where do you think the error should be?
Snippet/serializers.py
from rest_framework import serializers
from snippets.models import Snippet
class SnippetSerializer(serializers.Serializer):
class Meta:
model = Snippet
fields = ('id', 'title','code','linenos','language','style')
class SnippetSerializer(serializers.Serializer):
pk = serializers.Field() # Note: `Field` is an untyped read-only field.
title = serializers.CharField(required=False,
max_length=100)
code = serializers.CharField(widget=widgets.Textarea,
max_length=100000)
linenos = serializers.BooleanField(required=False)
language = serializers.ChoiceField(choices=LANGUAGE_CHOICES,
default='python')
style = serializers.ChoiceField(choices=STYLE_CHOICES,
default='friendly')
def restore_object(self, attrs, instance=None):
"""
Create or update a new snippet instance, given a dictionary
of deserialized field values.
Note that if we don't define this method, then deserializing
data will simply return a dictionary of items.
"""
if instance:
# Update existing instance
instance.title = attrs.get('title', instance.title)
instance.code = attrs.get('code', instance.code)
instance.linenos = attrs.get('linenos', instance.linenos)
instance.language = attrs.get('language', instance.language)
instance.style = attrs.get('style', instance.style)
return instance
# Create new instance
return Snippet(**attrs)
snippet/urls.py
from django.conf.urls import patterns, url
urlpatterns = patterns('snippets.views',
url(r'^snippets/$', 'snippet_list'),
url(r'^snippets/(?P<pk>[0-9]+)/$', 'snippet_detail'),
)
snippet/views.py
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
class JSONResponse(HttpResponse):
"""
An HttpResponse that renders its content into JSON.
"""
def __init__(self, data, **kwargs):
content = JSONRenderer().render(data)
kwargs['content_type'] = 'application/json'
super(JSONResponse, self).__init__(content, **kwargs)
#csrf_exempt
def snippet_list(request):
"""
List all code snippets, or create a new snippet.
"""
if request.method == 'GET':
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return JSONResponse(serializer.data)
elif request.method == 'POST':
data = JSONParser().parse(request)
serializer = SnippetSerializer(data=data)
if serializer.is_valid():
serializer.save()
return JSONResponse(serializer.data, status=201)
return JSONResponse(serializer.errors, status=400)
#csrf_exempt
def snippet_detail(request, pk):
"""
Retrieve, update or delete a code snippet.
"""
try:
snippet = Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
return HttpResponse(status=404)
if request.method == 'GET':
serializer = SnippetSerializer(snippet)
return JSONResponse(serializer.data)
elif request.method == 'PUT':
data = JSONParser().parse(request)
serializer = SnippetSerializer(snippet, data=data)
if serializer.is_valid():
serializer.save()
return JSONResponse(serializer.data)
return JSONResponse(serializer.errors, status=400)
elif request.method == 'DELETE':
snippet.delete()
return HttpResponse(status=204)
In your file urls.py, put quotes around snippets.urls.
Like this:
url(r'^',include('snippets.urls'))
Use serializers.ModelSerializer:
class SnippetSerializer(serializers.ModelSerializer):
class Meta:
model = Snippet
fields = ('id', 'title','code','linenos','language','style')
This lets DRF know to extract the fields and their types from the specified model class Snippet as parameters for the serializer. Your current version is using only serializers.Serializer. With the base Serializer class you need to define field variables for it to work. like this example:
class SnippetSerializer(serializers.Serializer):
pk = serializers.Field() # Note: `Field` is an untyped read-only field.
title = serializers.CharField(required=False,
max_length=100)
code = serializers.CharField(widget=widgets.Textarea,
max_length=100000)
linenos = serializers.BooleanField(required=False)
language = serializers.ChoiceField(choices=LANGUAGE_CHOICES,
default='python')
style = serializers.ChoiceField(choices=STYLE_CHOICES,
default='friendly')
Additionally, its probably a better practice for you to import your views from views.py like so:
from django.conf.urls import patterns, url
import views
urlpatterns = patterns('',
url(r'^snippets/$', views.snippet_list, name='snippet-list'),
url(r'^snippets/(?P<pk>[0-9]+)/$', views.snippet_detail, name='snippet-detail'),
)
for me, what solved the problem is the file name.
make sure that the file serializers itself have no spelling issues and his name is the same as in the code.
url.py
from django.conf.urls import patterns, include, url
import os.path
from crm.views import *
urlpatterns += patterns('',
(r'^test/$', tView.as_view()),
)
views.py
from django.views.generic import TodayArchiveView
from crm.forms import *
from crm.models import *
class tView(TodayArchiveView):
model = WorkDailyRecord
context_object_name = 'workDailyRecord'
date_field = 'date'
month_format = '%m'
template_name = "onlyWorkDailyRecord.html"
form_class = WorkDailyRecordForm ################## ADD
tView is generic view....
WorkDailyRecord is defining model in models.py
I want to pass form('WorkDailyRecordForm' class in forms.py) to template(onlyWorkDailyRecord.html)
How??
add
forms.py
from django import forms
class WorkDailyRecordForm(forms.Form):
contents = forms.CharField(label='',
widget=forms.Textarea(attrs={'placeholder': 'contents', 'style':'width:764px; height:35px;'}),
required=False,
)
target_user = forms.CharField(label='',
widget=forms.TextInput(attrs={'placeholder': 'target', 'style':'width:724px'}),
required=False,
)
onlyWorkDailyRecord.html
<form method="post" action="." class="form-inline" id="save-form">
{{ form.as_table }}
<button type="submit" class="btn btn-mini" id="workDailyRecord-add">작성</button>
</form>
In the class based views you need to mention the form_class:
form_class = WorkDailyRecordForm
A simple example:
class tView(TodayArchiveView):
form_class = WorkDailyRecordForm
template_name = 'onlyWorkDailyRecord.html'
model = WorkDailyRecord
def form_valid(self, form, **kwargs):
instance = form.save(commit=True)
return HttpResponse('Done')
def dispatch(self, request, *args, **kwargs):
return super(tView, self).dispatch(request, *args, **kwargs)
For more information read generic-editing in class based views
I had trouble with passing form class into a generic, I was able to do it by overriding the get_context_data() function, in case anyone was looking for this as well.
An example from the mozilla documentation:
class BookListView(generic.ListView):
model = Book
def get_context_data(self, **kwargs):
# Call the base implementation first to get the context
context = super(BookListView, self).get_context_data(**kwargs)
# Create any data and add it to the context
context['some_data'] = 'This is just some data'
return context
You would pass the form object into a key in the context dictionary.
Can find more information here.