Need guidance with FilteredSelectMultiple widget - python

I am sorry if it question might turn to be little broad, but since I am just learning django (and I am just hobbyist developer) I need some guidance which, I hope, will help someone like me in the future since I could not find any clear and easily comprehensible guide on using this widget. With your answers and help I will try to make this question thread at least guide-ish.
Material I found somewhat helpful for this topic:
Django multi-select widget?
Django: Replacement for the default ManyToMany Widget of Forms
Django's FilteredSelectMultiple widget only works when logged in
Django FilteredSelectMultiple not rendering on page
Use the Django admin app's FilteredSelectMultiple widget in form
Get the chosen values from FilteredSelectMultiple widget in Django
Django FilteredSelectMultiple Right Half Does Not Render
There were few others links, but they did not made anything clearer or added new information so I won't mention them.
Here is what I managed to understand (please correct me if I am wrong or add anything that I missed):
To create FilteredSelectMultiple widget firs I need to amend forms.py (as in any other widget creation process). Amended forms.py should have from django.contrib.admin.widgets import FilteredSelectMultiple import and Media class. forms.py code should look like this (please correct me, because probably it is wrong somewhere):
from django import forms
from catalog.models import DrgCode
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.conf import settings #without it I get an error that settings not defined
class CalculatorForm(forms.Form):
drg_choice = forms.ModelMultipleChoiceField(queryset=DrgCode.objects.all(), widget=FilteredSelectMultiple("Somethings", is_stacked=False), required=True)
class Media:
css = {
'all': (os.path.join(settings.BASE_DIR, '/static/admin/css/widgets.css'),),
}
js = ('/admin/jsi18n',)
Questions about this part:
Am I right about django.conf import? Since I did not see it
imported in any material I found. Answer: during my test I determined that django.conf import is necessary if using settings.BASE_DIR part. In various sources there was two ways of writing css path, this one worked for me.
Do I need to create widgets.css and corresponding directory? Or
django will find it itself? Since there is no such file or directory
generated after I created skeleton-website using django-admin
startproject cmd? Answer: No. There is no need to create widgets.css or any of the files since django finds them itself.
Same question as previous for jsi18n part. Also what is this? I
assume its javascript file, but it has no extension for some reason.
Also I cannot find it anywhere? Should I create it? How to do that?
Or I can copy it from somewhere? Partial answer: no need to create it. Just point at it in urls.py. Still do not know exactly that kind of file it is (or where it is)
After amending forms.py I should ammend urls.pyby adding url(r'^jsi18n/$', 'django.views.i18n.javascript_catalog', name='jsi18n')
So urls.py now look like this:
from django.urls import path
from . import views
from django.conf.urls import url
urlpatterns = [
path('', views.index, name='index'),
'django.views.i18n.javascript_catalog',
name='jsi18n'),
]
Questions:
Am I doing it wright or should I just add it below urlpatterns? Answer: This method if fine.
Now I need to set HTML template file for form to render (like in any other case). Code for it (file named DrgCalculator.html):
{% extends "base_generic.html" %}
<script type="text/javascript" src="{% url 'jsi18n' %}" > </script>
{{ form.media }}
<form enctype="multipart/form-data" method="POST">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="save btn btn-default">Submit</button>
</form>
This part seems more or less clear. But maybe I should amend
something or know about? Answer: Should be changed. Will write full code below.
Lastly I need to adjust views.py to set where this form and everything related with it happens.
From what I understand code in this part is more or less is not directly related with widget, but to complete everything and make working example I will use code I leaned/got in this (very good) django tutorial:
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.urls import reverse
from catalog.models import DrgCode
from catalog.forms import CalculatorForm
def DrgCalculator(request):
if request.method == 'POST':
form = CalculatorForm(request.POST)
if form.is_valid():
return render(request, 'DrgCalculator.html')
context = {
'form': form,
}
return render(request, 'DrgCalculator.html', context)
Questions:
Any remarks on this part of the code? Answer: Missing else: form = DrgCalculator(). Will write amended code below.
How I will access values which user choose using
FilteredSelectMultiple? I imagine I should clean data in
forms.py like with other widgets. So I should add nested function
below to my class CalculatorForm in forms.py am I right? Answer: Yes, data should be cleaned like in other cases. Function is correct.
def clean_CalculatorForm(self):
drg_choice = self.cleaned_data['drg_choice']
return drg_choice
Data I will get after cleaning will be list or dictionary? Am I
right? Answer: No, from this widget user input received as QuerySet
That is all my questions, sorry for long thread, I tried to make it as clear as possible. If I need to clarify something, please let me know. I will try to edit and update this, to make it friendly for people who will read it in future.
EDIT1: Answered some of my questions.
EDIT2: Answered rest of my questions.

After spending few days of research and testing I managed to get FilteredSelectMultiple widget working outside admin page in user form. As promised in question, I will try to synthesize my accumulated knowledge into some sort of guide which, I hope, will help someone like me in the future. Please note that I am far from professional (just a hobbyist with no computer engineering background to be precise) so my observations might not be exactly correct or the way I done it might not the best one. Despite that it might help you to get on the right track.
So to begin with FilteredSelectMultiple widget first of all - forms.py have to be amended by importing widget, creating field for it (similar like with regular widgets) and adding nested Media class. Code example:
from django.contrib.admin.widgets import FilteredSelectMultiple
class DrgCalculator(forms.Form):
drg_choise = forms.ModelMultipleChoiceField(queryset=DrgCode.objects.all(),
label="Something",
widget=FilteredSelectMultiple("Title", is_stacked=False),
required=True)
class Media:
css = {
'all': ('/static/admin/css/widgets.css',),
}
js = ('/admin/jsi18n',)
def clean_drg_choise(self):
drg_choise = self.cleaned_data['drg_choise']
return drg_choise
As I determined during my test and research class Media should be copied as written and require no changes for widget to work. Files mentioned in this class will be found by django itself so no need to search and copy them (like told in some of the material I read).
After creating form urls.py should be amended. During testing I found out, that in newer django versions (or at lest one I used) javascript_catalog is renamed and url provided cannot be string. So code should look like this:
from django.urls import path
from . import views
from django.conf.urls import url
from django import views as django_views
urlpatterns = [
url(r'^jsi18n/$', django_views.i18n.JavaScriptCatalog.as_view(), name='jsi18n'),
]
Now for the htlm template I am sure that there is more ways of doing it so I just provide example:
{% extends "base_generic.html" %}
{% block content %}
<div id='frame'>
<form action="" method="post">
<div id='sk_body'>
<fieldset>
<legend>Fill required fields</legend>
<form>
{% csrf_token %}
<table>
{{ form.media }}
{{ form.as_table }}
<script type="text/javascript" src="{% url 'jsi18n' %}"></script>
</table>
<input type="submit" value="Count">
</form>
</fieldset>
</div>
</form>
</div>
{% endblock %}
To receive data from this widget fairly standard code in views.py should be used, example code I used:
def DRG_calcualtor(request):
if request.method == 'POST':
form = DrgCalculator(request.POST)
if form.is_valid():
choosen_drg = form.cleaned_data['drg_choise'] #result as QuerySet
choosen_drg_list = list([str(i) for i in choosen_drg]) #you can convert it to list or anything you need
return render(request, 'DRGcalculator_valid.html')
context = {
'form': form,
}
return render(request, 'DRGcalculator.html', context)
else:
form = DrgCalculator()
context = {
'form': form,
}
return render(request, 'DRGcalculator.html', context)

Related

In Django, how would you have an html page that lists all objects in the database with a common attribute?

First off, I'm a rookie on the field so if I miss out any necessary details, please do let me know and I'll update ASAP.
Working with Django framework and SQLite database, I would like to have two html pages that list all items with the same "type" attribute.
Right now models.py looks like this (there are more attributes after this, but that doesn't matter here, I think):
class Articulo(models.Model):
MEDICINAL = 'med'
AUTOCULTIVO = 'cul'
TIPO_PROD = [
(MEDICINAL, 'Medicinal'),
(AUTOCULTIVO, 'Autocultivo'),
]
tipo = models.CharField(
max_length=3,
choices=TIPO_PROD,
default=MEDICINAL,
)
So I'd like for one of the html pages to list all the items with 'med' and another for all the items with 'cul'.
What I have tried is to write something similar to the search function to bring up those items by filtering that attribute, like this:
def medicinal(request):
items = Articulo.objects.filter(tipo__icontains=med)
return render(request, 'medicinales.html', {'articulos': articulos})
However, I'm really not sure how to continue from there.
I also want to add CSS to the list once it's displayed, but for that I will replicate the one I use for the search function, since I want them to retain the same style.
Thank you very much in advance and again, please let me know if I missed out some important information.
You don't need to use two views to do this, you can define one ListView, and then filter the data shown in the list by defining a get_queryset function in your view. Here is a quick example to give you an idea:
urls.py
path(r'list/', ArticuloListView.as_view(), name='articulo-list')
views.py
class ArticuloListView(ListView):
model = Articulo
context_object_name = 'articulos'
template = 'artucilo_list.html' #this path may differ depending on your project structure
def get_queryset(self):
search_term = self.request.GET['q'] #'q' is defined in your search form
return Articulo.objects.filter(tipo=search_term)
search template
...
<form action="{% url 'articulo-list' %}" method='GET'>
<input name="q" type="text" value="{{ request.GET.q }}" placeholder="Search"/>
</form>
...
articulo_list.html template
...
{% for articulo in articulos %}
{{ articulo.tipo }}
{% endfor %}
...
You dont have to filter based on a search form, you can also manually do it by using the querystring in a link href, like this:
Med List
I have managed to get it done by using a queryset as iri suggested, but in a simpler way.
In urls.py
path('medicinales/', productos_med, name='medicinales'),
In views.py:
def productos_med(request):
queryset = Articulo.objects.filter(tipo='med')
context = {
"object_list": queryset
}
return render(request, "medicinales.html", context)
And in medicinales.html (the page with the list of all 'med' objects):
{% for instance in object_list %}
<p>{{instance.nombre}}</p>
<p>{{instance.descripcion}}</p> <!-- And so on for every wished attribute. -->
Then for objects with attribute 'cul' I followed the same steps changing tipo in queryset to 'cul' and rendering a different html page. After that applying CSS was easy!
Thank you very much iri for answering and pointing me in the right direction and the community for being such an awesome source of info!

How to display my python code in a table on Django webpage?

I have written some code in Python that reads in two strings, removes the punctuation and then compares the words in them within a matrix table which it prints to the console.
How do I convert the code to be utilised within the Django framework. I want to display a similar matrix on the web. I've already imported it into views. Please may someone point me in the right direction? I've been using django project and lynda to learn as I go along,
Edit:
Merci for the help guys. Managed to get it to display on a webpage. But it is printing it all out as a single string. How do I style it a bit better?
Think of passing your data to a "Django webpage" as just passing a dictionary of your values to a Django template from your Django view.
What is a Django template?
A Django template is the 'T' in Django's 'MTV' design pattern. In the conventional MVC design pattern (Model-View-Controller), the View is where you display things. In Django, Templates are where you display things. Oddly enough, the 'View' in Django is actually the Controller. This took me a while to wrap my head around.
Why do we use a dictionary-like context?
By mapping keys to values we achieve super-fast [O(1)/constant] lookup in the Django templates.
With all of this in mind, I'd advocate using 'TemplateView' generic view, doing your work in a utils file, importing utils into views, and then passing your data to the template via the context dictionary. So it would look something like this:
local_utils.py
import string
import pandas as pd
pd.set_option('display.max_columns', None)
def generate_out_matrix():
with open('./arrayattempts/samp.txt', 'r') as file1:
sampInput=file1.read().replace('\n', '')
#print(sampInput)
with open('./arrayattempts/ref.txt', 'r') as file2:
refInput=file2.read().replace('\n', '')
#print(refInput)
sampArray = [word.strip(string.punctuation) for word in sampInput.split()]
refArray = [word.strip(string.punctuation) for word in refInput.split()]
out=pd.DataFrame(index=refArray,columns=sampArray)
for i in range(0, out.shape[0]):
for word in sampArray:
out.ix[i,str(word)] = out.index[i].count(str(word))
return out.as_matrix()
views.py
from appname.local_utils import generate_out_matrix
class Detail(TemplateView):
template_name = 'appname/yourhtml.html'
# Will render on each call to URL for 'Detail'
def get_context_data(self):
out = generate_out_matrix()
context['out'] = out
return context
appname/templates/yourhtml.html
{% if out %}
{% for row in out_matrix %}
{% for o in row %}
{{ o }}
{% endfor %}
<br>
{% endfor %}
{% endif %}
urls.py
path('/your_path', views.Detail.as_view()),
https://docs.djangoproject.com/en/2.0/ref/templates/api/#rendering-a-context
To send your data to your template you should add your variable to context at your views
from django.http import Http404
from django.shortcuts import render
from polls.models import Poll
def detail(request, poll_id):
... // your logic
out // your variable
return render(request, 'yourhtml.html', {'out': out})
In html will be like that
{{ out }}
{% for o in out %}
{{ o }}
{% endfor %}
https://docs.djangoproject.com/en/2.0/topics/http/views/
You can style your table with some CSS or using ny lib struct to handle tables
You can follow this guide
display django-pandas dataframe in a django template

Django - how to delete model item from list

I'm currently building an app with Django for the purpose of creating user-story cards (a bit like a Trello board).
On one page I have my cards displayed as a list:
The code for the list is:
<h1>ScrumBuddy Board</h1>
<ul>
{% for card in cards.all %}
<li class="card">{{ card.title }}
</li>
{% endfor %}
</ul>
And the view def for the board is:
def board(request):
cards = Card.objects
context = {'cards': cards}
return render(request, 'scrumbuddy/board.html', context)
I'd like to add a delete link to each card that removes it from this list, preferable with a confirmation dialogue box. Any suggestions on how to do that would be fantastic.
Many thanks.
I'd suggest using Django's generic class-based views, in your case you would probably use ListView and DeleteView.
For a list view, you could use something like this:
# views.py
from django.views.generic import ListView
from scrumbuddy.models import Card
class CardList(ListView):
model = Card
And for the delete view something like this:
# views.py
from django.views.generic.edit import DeleteView
from django.urls import reverse_lazy
from scrumbuddy.models import Card
class CardDelete(DeleteView):
model = Card
success_url = reverse_lazy('card-list')
As far as the confirmation for the delete action goes, from the Django documentation on the DetailView topic:
A view that displays a confirmation page and deletes an existing
object. The given object will only be deleted if the request method is
POST. If this view is fetched via GET, it will display a confirmation
page that should contain a form that POSTs to the same URL.
Also, note that you can pass arguments to the url template tag, e.g.:
<a href="{% url 'card' id=card.id %}">
instead of:
<a href="{% url 'card' %}/{{ card.id }}">
Note: I guessed that the card.id parameter to your card url is named id - you'll have to update this as per your urls.py if the parameter is named differently.

Django-Registration: How to alter the form (need to add submit button that works!)

So I am trying to alter one of the forms from Django-registration: an app I installed via pip.
As stated in the docs, I was to created a registration/registration_form.html and use form as my context:
<html>
<p>This is the registration form</p>
<ul>
{{ form.as_ul}}
</ul>
</html>
So I have 2 questions:
1) How am I to alter the form to have a submit button that actually works in this case?
2) How can I alter the django-registration models so that I can ultimately add more to the registration forms?
Yes I looked over the docs, I am asking here because the language confused me and seemed slightly advance for me.
Thank you!
You need to add the form tags and a submit button. Something like this:
<html>
<p>This is the registration form</p>
<form action="/url/to/register/" method="POST">
{{form.as_ul}}
<input type="submit" value="Register">
</form>
</html>
where "/url/to/register/" will need to be pointed at your view code in your urls.py. Something like this:
from django.conf.urls import url, patterns
from yoursite.registrations import views
urlpatterns = patterns('',
url(r'^url/to/register/', views.register_some_guy),
)

Change Django Templates Based on User-Agent

I've made a Django site, but I've drank the Koolaid and I want to make an IPhone version. After putting much thought into I've come up with two options:
Make a whole other site, like i.xxxx.com. Tie it into the same database using Django's sites framework.
Find some time of middleware that reads the user-agent, and changes the template directories dynamically.
I'd really prefer option #2, however; I have some reservations, mainly because the Django documentation discourages changing settings on the fly. I found a snippet that would do the what I'd like. My main issue is having it as seamless as possible, I'd like it to be automagic and transparent to the user.
Has anyone else come across the same issue? Would anyone care to share about how they've tackled making IPhone versions of Django sites?
Update
I went with a combination of middleware and tweaking the template call.
For the middleware, I used minidetector. I like it because it detects a plethora of mobile user-agents. All I have to do is check request.mobile in my views.
For the template call tweak:
def check_mobile(request, template_name):
if request.mobile:
return 'mobile-%s'%template_name
return template_name
I use this for any view that I know I have both versions.
TODO:
Figure out how to access request.mobile in an extended version of render_to_response so I don't have to use check_mobile('template_name.html')
Using the previous automagically fallback to the regular template if no mobile version exists.
Rather than changing the template directories dynamically you could modify the request and add a value that lets your view know if the user is on an iphone or not. Then wrap render_to_response (or whatever you are using for creating HttpResponse objects) to grab the iphone version of the template instead of the standard html version if they are using an iphone.
Detect the user agent in middleware, switch the url bindings, profit!
How? Django request objects have a .urlconf attribute, which can be set by middleware.
From django docs:
Django determines the root URLconf
module to use. Ordinarily, this is the
value of the ROOT_URLCONF setting, but
if the incoming HttpRequest object has
an attribute called urlconf (set by
middleware request processing), its
value will be used in place of the
ROOT_URLCONF setting.
In yourproj/middlware.py, write a class that checks the http_user_agent string:
import re
MOBILE_AGENT_RE=re.compile(r".*(iphone|mobile|androidtouch)",re.IGNORECASE)
class MobileMiddleware(object):
def process_request(self,request):
if MOBILE_AGENT_RE.match(request.META['HTTP_USER_AGENT']):
request.urlconf="yourproj.mobile_urls"
Don't forget to add this to MIDDLEWARE_CLASSES in settings.py:
MIDDLEWARE_CLASSES= [...
'yourproj.middleware.MobileMiddleware',
...]
Create a mobile urlconf, yourproj/mobile_urls.py:
urlpatterns=patterns('',('r'/?$', 'mobile.index'), ...)
I'm developing djangobile, a django mobile extension: http://code.google.com/p/djangobile/
You should take a look at the django-mobileadmin source code, which solved exactly this problem.
Other way would be creating your own template loader that loads templates specific to user agent. This is pretty generic technique and can be use to dynamically determine what template has to be loaded depending on other factors too, like requested language (good companion to existing Django i18n machinery).
Django Book has a section on this subject.
There is a nice article which explains how to render the same data by different templates
http://www.postneo.com/2006/07/26/acknowledging-the-mobile-web-with-django
You still need to automatically redirect the user to mobile site however and this can be done using several methods (your check_mobile trick will work too)
How about redirecting user to i.xxx.com after parsing his UA in some middleware? I highly doubt that mobile users care how url look like, still they can access your site using main url.
best possible scenario: use minidetector to add the extra info to the request, then use django's built in request context to pass it to your templates like so
from django.shortcuts import render_to_response
from django.template import RequestContext
def my_view_on_mobile_and_desktop(request)
.....
render_to_response('regular_template.html',
{'my vars to template':vars},
context_instance=RequestContext(request))
then in your template you are able to introduce stuff like:
<html>
<head>
{% block head %}
<title>blah</title>
{% if request.mobile %}
<link rel="stylesheet" href="{{ MEDIA_URL }}/styles/base-mobile.css">
{% else %}
<link rel="stylesheet" href="{{ MEDIA_URL }}/styles/base-desktop.css">
{% endif %}
</head>
<body>
<div id="navigation">
{% include "_navigation.html" %}
</div>
{% if not request.mobile %}
<div id="sidebar">
<p> sidebar content not fit for mobile </p>
</div>
{% endif %>
<div id="content">
<article>
{% if not request.mobile %}
<aside>
<p> aside content </p>
</aside>
{% endif %}
<p> article content </p>
</aricle>
</div>
</body>
</html>
A simple solution is to create a wrapper around django.shortcuts.render. I put mine in a utils library in the root of my application. The wrapper works by automatically rendering templates in either a "mobile" or "desktop" folder.
In utils.shortcuts:
from django.shortcuts import render
from user_agents import parse
def my_render(request, *args, **kwargs):
"""
An extension of django.shortcuts.render.
Appends 'mobile/' or 'desktop/' to a given template location
to render the appropriate template for mobile or desktop
depends on user_agents python library
https://github.com/selwin/python-user-agents
"""
template_location = args[0]
args_list = list(args)
ua_string = request.META['HTTP_USER_AGENT']
user_agent = parse(ua_string)
if user_agent.is_mobile:
args_list[0] = 'mobile/' + template_location
args = tuple(args_list)
return render(request, *args, **kwargs)
else:
args_list[0] = 'desktop/' + template_location
args = tuple(args_list)
return render(request, *args, **kwargs)
In view:
from utils.shortcuts import my_render
def home(request): return my_render(request, 'home.html')

Categories

Resources