I am using below code to add meta keywords -
in view.py
#template_render("mysite/category.html")
def category(request, slug):
slug = slug.lower()
product_type = local_settings.CATEGORY_NAME_TO_ID.get(slug, False)
if not product_type:
raise Http404
products = models.Product.objects.active().filter(product_type = product_type).all()
return { 'products' : products, 'slug' : slug, 'key':'wholesale ipad, ipad with retina display, ipad mini, ipad 3, ipad 2',}
And in template file -
{% extends "base.html"%}
{%load appletrade_tags%}
{% block key %}siteTrade - {{key}}{% endblock %}
{%block title%}site Trade - {{slug}}{%endblock%}
But it's not reflecting. I have checked in view source there is no keyword.
But Yes,title is reflecting.
Can you please help me to find out where I am wrong ?
EDIT :
base.html
{% extends "base.html"%}
{% block key %}{%if page.key%}{{page.key}}{%else%}{{block.super}}{%endif%}{% endblock %}
{% block desc %}{%if page.desc%}{{page.desc}}{%else%}{{block.super}}{%endif%}{% endblock %}
{%block title%}{%if page.title%}{{page.title}}{%else%}{{block.super}}{%endif%}{%endblock%}
{%block content%}
{%endblock%}
You need to be using either render or render_to_response to pass a context to the template. Is the slug object appearing on the page?
from django.shortcuts import render_to_response
def category(request, slug):
slug = slug.lower()
product_type = local_settings.CATEGORY_NAME_TO_ID.get(slug, False)
if not product_type:
raise Http404
products = models.Product.objects.active().filter(product_type = product_type)
context = {
'slug': slug,
'products': products,
'key': 'wholesale ipad, ipad with retina display, ipad mini, ipad 3, ipad 2',
}
return render_to_response('appletrade/category.html', context, context_instance=RequestContext(request))
Here is a way to automate keywords for your django site. I don't like manually entering things anyways.
Here's a function to read your template file and count primary words, and return the list of the words used in the page.
# meta_gen.py
# create meta keywords for webpage automagically
# MIT License
# Author: Daniel P. Clark 6ftdan#gmail.com
import collections, string, StringIO, re
from django.utils.html import strip_tags
# Counts words in template files and insert keywords into page
word_count_min = 2
word_length_min = 4
nogo_list = [] # Optional list of words you may not want as meta tags.
# meta_keywords ( html string ) =>
# returns non html keyword list, as a comma seperated string,
# for words fitting qualifications as chosen above.
def meta_keywords(str_file):
c = collections.Counter()
strIO_Obj = StringIO.StringIO(str_file)
for line in strIO_Obj.readlines():
c.update(re.findall(r"[\w']+", strip_tags(line).lower()))
wordlist = []
for word, count in c.most_common():
if len(word) > (word_length_min-1) and count > (word_count_min-1) and word not in nogo_list: wordlist.append(word)
return string.join(wordlist, ',')
Place meta_gen.py in your main project folder. Then add these pieces to each of your views.py files.
# views.py
from django.shortcuts import render_to_response
from django.template.loader import render_to_string
from project.meta_gen import meta_keywords
this_template = "apptemplate.html"
def tabs(request):
return render_to_response(this_template, { 'title' : "Page Name", 'keys' : meta_keywords(render_to_string(this_template)) })
And lastly in your main template base.html you place the meta tag for keywords.
# base.html
<head>
<title>Site Name - {{ title }}</title>
<meta name="keywords" content="{{ keys }}" />
</head>
And that's it. All pages that inherit the base template and have the views.py code will insert keywords meta tags with words that repeat on your pages.
I realize that this can be improved upon and optimized. Speed isn't a concern for me. So input is welcome.
Related
I am attempting the solution mentioned in this stack overflow post (Adding a button to Wagtail Dashboard) however the solution might be outdated, or at least it doesn't work for me and I'm unsure why.
Goal: Be able to export a object's data to csv
First, the button HTML code had to be slightly adjusted to be formatted correctly like so:
{% extends "modeladmin/index.html" %}
{% block header_extra %}
<div class="right">
<div class="addbutton" style="margin-left: 2em;">
{% include "modeladmin/includes/button.html" with button=view.button_helper.export_button %}
My button
</div>
</div>
{{ block.super }}{% comment %}Display the original buttons {% endcomment %}
{% endblock %}
and then I copied and pasted the views and helper functions:
from django.contrib.auth.decorators import login_required
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.utils.functional import cached_property
from django.utils.translation import ugettext as _
from wagtail.contrib.modeladmin.helpers import AdminURLHelper, ButtonHelper
from wagtail.contrib.modeladmin.views import IndexView
class ExportButtonHelper(ButtonHelper):
"""
This helper constructs all the necessary attributes to create a button.
There is a lot of boilerplate just for the classnames to be right :(
"""
export_button_classnames = ['icon', 'icon-download']
def export_button(self, classnames_add=None, classnames_exclude=None):
if classnames_add is None:
classnames_add = []
if classnames_exclude is None:
classnames_exclude = []
classnames = self.export_button_classnames + classnames_add
cn = self.finalise_classname(classnames, classnames_exclude)
text = _('Export {}'.format(self.verbose_name_plural.title()))
return {
'url': self.url_helper.get_action_url('export', query_params=self.request.GET),
'label': text,
'classname': cn,
'title': text,
}
class ExportAdminURLHelper(AdminURLHelper):
"""
This helper constructs the different urls.
This is mostly just to overwrite the default behaviour
which consider any action other than 'create', 'choose_parent' and 'index'
as `object specific` and will try to add the object PK to the url
which is not what we want for the `export` option.
In addition, it appends the filters to the action.
"""
non_object_specific_actions = ('create', 'choose_parent', 'index', 'export')
def get_action_url(self, action, *args, **kwargs):
query_params = kwargs.pop('query_params', None)
url_name = self.get_action_url_name(action)
if action in self.non_object_specific_actions:
url = reverse(url_name)
else:
url = reverse(url_name, args=args, kwargs=kwargs)
if query_params:
url += '?{params}'.format(params=query_params.urlencode())
return url
def get_action_url_pattern(self, action):
if action in self.non_object_specific_actions:
return self._get_action_url_pattern(action)
return self._get_object_specific_action_url_pattern(action)
class ExportView(IndexView):
"""
A Class Based View which will generate
"""
def export_csv(self):
data = self.queryset.all()
response = ...
return response
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
super().dispatch(request, *args, **kwargs)
return self.export_csv()
class ExportModelAdminMixin(object):
"""
A mixin to add to your model admin which hooks the different helpers, the view
and register the new urls.
"""
button_helper_class = ExportButtonHelper
url_helper_class = ExportAdminURLHelper
export_view_class = ExportView
def get_admin_urls_for_registration(self):
urls = super().get_admin_urls_for_registration()
urls += (
url(
self.url_helper.get_action_url_pattern('export'),
self.export_view,
name=self.url_helper.get_action_url_name('export')
),
)
return urls
def export_view(self, request):
kwargs = {'model_admin': self}
view_class = self.export_view_class
return view_class.as_view(**kwargs)(request)
and then I added the ModelAdmin to the hooks. I'm able to see the button however it doesn't function as it's supposed to (exporting the csv). In fact, it does absolutely nothing (the href is # after all) and I feel like I'm missing some steps.
I used the same implementation as you.
I guess that you are having problems in the def export_csv(self) method
here my implementation
from djqscsv.djqscsv import render_to_csv_response
class ExportView(IndexView):
model_admin = None
def export_csv(self) -> dict:
if (self.model_admin is None) or not hasattr(
self.model_admin, "csv_export_fields"
):
data = self.queryset.all().values()
else:
data = self.queryset.all().values(*self.model_admin.csv_export_fields)
return render_to_csv_response(data)
#method_decorator(login_required)
def dispatch(self, request: HttpRequest, *args: list, **kwargs: dict) -> dict:
super().dispatch(request, *args, **kwargs)
return self.export_csv()
csv_export_fields can be added to your model admin to specify which fields you want to export
here I am adding the HTML file:
{% extends "modeladmin/index.html" %}
{% block header_extra %}
{% include 'modeladmin/includes/button.html' with button=view.button_helper.export_button %}
{{ block.super }}
{% endblock %}
your admin.py
class MyModelAdmin(ModelAdmin, ExportModelAdminMixin):
model = MyModel
menu_icon = 'tag'
menu_order = 200
index_template_name = "wagtailadmin/export_csv.html"
csv_export_fields = [ "field_name","field_name_1", ]
list_display = ('first_name', 'last_name'')
search_fields = ('first_name', 'last_name',)
I'm passing a bunch of data into my template but am having a hard time breaking apart a zipped list of items. No matter what I try, I always get the following error.
Need 2 values to unpack in for loop; got 0.
Heres my code:
views.py
import requests
from django.shortcuts import render
from django.http import HttpResponse
dictionary, words = [[], []], []
def home(request, username='johnny'):
template_name = 'main/index.html'
url = "https://www.duolingo.com/users/{}".format(username)
getUserData(url)
context = {
'username': username,
'dictionary': dictionary,
'words': words,
}
# print(context)
return render(request, template_name, context)
def getUserData(url):
response = requests.get(url)
userdata = response.json()
wordlists, explanations = [], []
for language in userdata['language_data']:
for index in userdata['language_data'][language]['skills']:
if index.get('levels_finished') > 0:
wordList = index.get("words")
wordlists.append(wordList)
explanations.append(index.get("explanation"))
for wordItem in wordList:
words.append(wordItem)
dictionary = list(zip(wordlists, explanations))
relevant template
{% block content %}
{% for words, exp in dictionary %}
{{ words }}
{{ exp|safe }}
{% endfor %}
{% endblock %}
I've tested this code, it works.
Once I refactored in Django to put wordLists in an array with explanations, things go to hell. If I print(dictionary) at the end of the method, the data shows in the console. Not sure what else I'm missing.
Your problem is with scope. the dictionary(variable) which you are returning from home function(as context) and dictionary in getUserData function are not in same scope. So whenever you are updating getUserData method's dictionary, its not being updated in home. I would not recommend your approach for dictionary as its using global variable. I would recommend something like this:
def getUserData(url):
response = requests.get(url)
userdata = response.json()
wordlists, explanations, words = [], [], []
for language in userdata['language_data']:
for index in userdata['language_data'][language]['skills']:
if index.get('levels_finished') > 0:
wordList = index.get("words")
wordlists.append(wordList)
explanations.append(index.get("explanation"))
for wordItem in wordList:
words.append(wordItem)
return list(zip(wordlists, explanations)), words # return the value of dictionary from here
def home(request, username='johnny'):
template_name = 'main/index.html'
url = "https://www.duolingo.com/users/{}".format(username)
dictionary, words = getUserData(url) # catch value of dictionary
context = {
'username': username,
'dictionary': dictionary,
'words': words,
}
# print(context)
return render(request, template_name, context)
I meet difficulties as below :
I have a blog page. In blog ,i create 'comment' function to comment post. And comments has 'like' function. For this ,i create two view function ,one of them simple function ,second is api function. And create jquery ajax for to call api function. After api calling ,it update data in db. Problem is :
If i create two comment ,ajax function works only for first comment for to like comment. It looks like ,for first comment CommentLikeAPIToggle works ,for next comments CommentLikeToggle works. Here is my codes :
views.py
class CommentLikeToggle(RedirectView):
def get_redirect_url( self, *args, **kwargs):
id = self.kwargs.get('id')
obj = get_object_or_404(Comment,id=id)
url_ = obj.content_object.get_absolute_url()
user = self.request.user
if user.is_authenticated():
if user in obj.likes.all():
obj.likes.remove(user)
else:
obj.likes.add(user)
return url_
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import authentication, permissions
class CommentLikeAPIToggle(APIView):
authentication_classes = (authentication.SessionAuthentication,)
permission_classes = (permissions.IsAuthenticated,)
def get(self, request,id=None, format=None):
obj = get_object_or_404(Comment,id=id)
url_ = obj.get_absolute_url()
user = self.request.user
updated = False
liked = False
if user.is_authenticated():
if user in obj.likes.all():
liked = False
obj.likes.remove(user)
else:
liked = True
obj.likes.add(user)
updated = True
data = {
'updated':updated,
'liked':liked
}
return Response(data)
Ajax function :
function updateComLike (newCount,btn,verb){
btn.text(" "+newCount+ " " + verb);
btn.attr({"data-likes": newCount,"class":"fa fa-thumbs-up"})
}
$("#com-like").click(function(e){
e.preventDefault()
var this_ = $(this)
var likeUrl = this_.attr("data-href")
var likeCount = parseInt(this_.attr("data-likes"))
$.ajax({
url: likeUrl,
method: "GET",
data : {},
success: function(data){
var newLikes;
if (data.liked){
newLikes = likeCount + 1
updateComLike(newLikes,this_ ,gettext("Unlike"))
} else {
newLikes = likeCount - 1
updateComLike(newLikes,this_ ,gettext("Like"))
}
}, error: function(error){
}
})
})
Template tag :
{% for comment in comments %}
{{ comment.content }}
<footer>
<a data-href="{{comment.get_api_com_like_url}}" data-likes="
{{comment.likes.count}}" href="{{comment.get_com_like_url}}" id="com-like">
<i class="fa fa-thumbs-up"></i> {{comment.likes.count}}
{% if request.user in comment.likes.all %} {% trans "Unlike" %}
{%else%}{%trans "Like" %}{% endif %}
</a>
</footer>
{% endfor %}
Urls :
url(r'^api/(?P<id>\d+)/com-like/$',CommentLikeAPIToggle.as_view(), name='com-like-api-toggle'),
url(r'^(?P<id>\d+)/com-like/$',CommentLikeToggle.as_view(), name='com-like-toggle'),
I have found my problem and solved it myself. The problem is :i'm using id in template tags. And id should be unique to each element. So i used class instead of id and problem fixed
{{comment.likes.count}}" href="{{comment.get_com_like_url}}" class="com-like">
And in the ajax
$('a.com-like').click(function(e){
My first foray into Django, allows the user to input a day of the week, and searches my database for restaurants open on that given day. Right now, the restaurant objects (Resto) have a days_open attribute, with each day separated by a comma (Monday, Tuesday, etc...).
When I input a day, the resulting page only displays the title and '[]' and I can't seem to get it to return the list of Resto objects. What is displaying the square brackets, and how do I go about fixing it to display the results of the search?
The code is below- my apologies if I neglected to include any relevant bits.
my forms.py:
from django import forms
from .models import Resto
class RestoSearch(forms.ModelForm):
class Meta:
model = Resto
fields = ('title', 'description', 'opening_hour', 'closing_hour')
models.py:
from django.db import models
class Resto(models.Model):
title = models.CharField(max_length=300)
description = models.TextField()
opening_hour = models.TimeField(auto_now=False, auto_now_add=False, null=True)
closing_hour = models.TimeField(auto_now=False, auto_now_add=False, null=True)
days_open = models.TextField(blank=True)
views.py:
from django.shortcuts import render
from django.http import Http404
from django.shortcuts import HttpResponse
from belize.models import Resto
from django.core.exceptions import *
from .forms import RestoSearch
def index(request):
return render(request, 'form.html')
def search(request):
form = RestoSearch()
if request.method == 'POST':
search_id=request.POST.get('textfield', None)
try:
#I think this is where my problem is
available = Resto.objects.filter(days_open = search_id)
html = ("<H1>Hello</H1>", available)
return HttpResponse(html)
except Resto.DoesNotExist:
return HttpResponse("Please try another day")
else:
return render(request, 'belize/form.html')
def restaurant_detail(request, id):
try:
restaurant = Resto.objects.get(id=id)
except Resto.DoesNotExist:
raise Http404('This restaurant does not exist')
return render(request, 'belize/restaurant_detail.html', {
'restaurant': restaurant,
})
template form.html:
<form method="POST" action="/search/">
{% csrf_token %}
<input type="text" name="textfield">
<button type="submit">Enter a day of the week</button>
</form>
I presume what you are trying to show is the RestoForm in that case the index method is not correct. It should be
def index(request):
form = RestoForm()
return render(request, 'form.html', {'form': form })
And then your template should change as
<form method="POST" action="/search/">
{% csrf_token %}
{{ form }}
<button type="submit">Enter a day of the week</button>
</form>
For additional details please see the examples at https://docs.djangoproject.com/en/1.9/topics/forms/#the-template
The [] means that your .filter() returned no results, its not surprising as you have a few issues with your code, lets start from the top:
You are declaring a form that you never use.
You are trying to catch an exception that is never raised by .filter()
You filter condition will only work for exact matches.
I've annotated your code as well:
def search(request):
form = RestoSearch() # You aren't using this form anywhere in your code?
if request.method == 'POST':
# Where is 'textfield' coming from?
search_id = request.POST.get('textfield', None)
try:
# If search id is "Tuesday", and a restaurant is
# open on monday and tuesday, so it has "Monday,Tuesday"
# in the days_open field, then this search will not
# return any results, because its looking for an exact
# match
available = Resto.objects.filter(days_open=search_id)
html = ('<H1>Hello World</H1>', available)
return HttpResponse(html)
except Resto.DoesNotExist:
# This exception is not raised by .filter(),
# .filter() will return an empty list, [] if no results are found
# so this entire try/except is not doing anything
return HttpResponse("Please try another day")
else: # in your code, this else is not indented correctly
return render(request, 'belize/form.html')
So there is a lot going on here, lets try something simple, starting with the template:
{% if results %}
{% for restaurant in results %}
{{ restaurant }}
{% endfor %}
{% else %}
Sorry, no results for your search. Try again.
{% endif %}
<form>
{{ form }}
<input type="submit" name="Search" />
</form>
Next, the search form:
class SearchForm(forms.Form):
search_field = forms.CharField('Search', strip=True)
Finally the view:
from django.db.models import Q
def search(request):
form = SearchForm(request.GET)
results = [] # Set results to an empty list
if form.is_valid():
needle = form.cleaned_data['search_field'].capitalize()
results = Resto.objects.filter(Q(days_open__startswith='{},'.format(needle)) |
Q(days_open__endswith=',{}'.format(needle)) |
Q(days_open__contains=',{},'.format(needle)) |
Q(days_open='{},'.format(needle)) |
Q(days_open='{}'.format(needle)))
return render(request, 'search.html', {'results': results, 'form': form})
Lets assume the user entered 'Thursday' as a search field. In this view you are searching for all restaurants whose days_open field:
Either starts with Thursday, or
Ends with ,Thursday or
Contains ,Thursday, in the middle or
Has the value Thursday, or
Has the value Thursday
Your template will then only show the results if there are any values to display; since an empty list [] is false, then the {% if results %} condition will fail, so on empty lists the template will display the error notice instead.
In your view, you only do the database check if someone enters something in the search field, that's what if form.is_valid(): does. In django, by default all form fields are required - so a blank form will return an error. Using this trick, we make sure we only search if someone enters a value in the search box.
The main action happens with all the Q() calls. A Q object is a way to do multiple queries and chain them together. It is used whenever you want to do an "or" or "and" type query. Our search is an "or" type query, because we want to return any results if the value in days_open matches any number of conditions.
I created a function called getData in effort to cut down 4 nested "if" statement inside my userInfo method. The result was devastating. I'm being humiliated by the fact that the page didn't proceed to my successful.html template. If I move everything inside getData method back to the userInfo function, everything return to normal. Is there a trick to making it work so I can restore my shame?
views.py
def userInfo (request):
# Set maximum to avoid default of 1000 forms.
UserFormSet = formset_factory (UserForm, formset = BaseUserFormSet, extra = 2, max_num = 5)
if request.method == 'POST':
formset = UserFormSet (request.POST)
if formset.is_valid ():
location = request.POST ['site']
data = formset.cleaned_data
getData (request, data, location) # ====> Created a function to cut down nested if statement
else:
formset = UserFormSet ()
...
def getData (request, data, location):
validUser = []
for form in data:
username = form.get ('user_name')
userID = form.get ('user_ID')
if username and userID:
n = register (username, userID, location)
if n.checkDataBase ():
validUser.append (username)
if validUser:
context = {'validUser': validUser}
return render (request, 'userform/success.html', context)
HTML
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>Successfully Added</title>
</head>
<body>
<h1>User Information:</h1>
<ul>
{% for user in validUser %}
<li>{{ user }}</li>
{% endfor %}
</ul>
Add more users
</body>
</html>
Does it work if you change your getData() to:
if validUser:
context = {'validUser': validUser}
return request, 'userform/success.html', context
and your userInfo() to:
if formset.is_valid ():
location = request.POST ['site']
data = formset.cleaned_data
request, template, context = getData (request, data, location) # ====> Created a function to cut down nested if statement
return render (request, template, context)
try
return getData (request, data, location)
(add return statement).