Django - Pass object into custom form field - django

I am trying to create a rating form field using the jquery code here
So far I have it working fine but what I need to do is pass in a url based on the object I am trying to rate. See line $.post("URL TO GO HERE", {rating: value}, function(db) is code below. The url would be something like /rating/object_id where object_id would be the pk of the object I want to rate. What is the best way to pass in a object id so I can use it. Would I need to pass it into RatingField first and then pass it from there into StarWidget?
class StarWidget(widgets.Select):
"""
widget to show stars which the user can click on to rate
"""
class Media:
css = {
'all': ('css/ui.stars.css',)
}
js = ('js/ui.stars.js',)
def render(self, name, value, attrs=None):
output = super(StarWidget, self).render(name, value, attrs)
jquery = u"""
<div id="stars-rating" class="rating_section">
%s
<span id="caption"></span>
</div>
<script type="text/javascript">
$(function(){
$("#stars-rating").stars({
inputType: "select",
captionEl: $("#caption"),
cancelShow: false,
callback: function(ui, type, value)
{
// Hide Stars while AJAX connection is active
$("#stars-rating").hide();
$("#loader").show();
$.post("URL TO GO HERE", {rating: value}, function(db)
{
$("#loader").hide();
$("#stars-rating").show();
}, "json");
}
});
});
</script>
""" % (output)
return mark_safe(jquery)
class RatingField(forms.ChoiceField):
"""
rating field. changes the widget and sets the choices based on the model
"""
widget = StarWidget
def __init__(self, *args, **kwargs):
super(RatingField, self).__init__(*args, **kwargs)
self.label = "Rating:"
self.initial = 3
self.choices = Rating.RATING_CHOICES

I know the built-in fields are done this way, but it's really not good practice to embed large amounts of HTML or JS into the Python code. Instead, create a separate template fragment which is rendered by the field's render method. You can pass in the object's ID for use in the template {% url %} function, or just pass in the entire URL via call to reverse.

Related

Django Admin: Add Hyperlink to related model

I would like to add a hyperlink to the related model Training
It would be nice to have declarative solution, since I want to use
this at several places.
The "pencil" icon opens the related model in a popup window. That's not what I want. I want a plain hyperlink to the related model.
BTW, if you use "raw_id_fields", then the result is exactly what I was looking for: There is a hyperlink to the corresponding admin interface of this ForeignKey.
Update Jan 4, 2023
From Django 4.1, this becomes a part of the official build (related PR).
Related widget wrappers now have a link to object’s change form
Result
Previous Answer
The class named RelatedFieldWidgetWrapper is showing the icons on the Django Admin page and thus you need to override the same. So, create a custom class as below,
from django.contrib.admin.widgets import RelatedFieldWidgetWrapper
class CustomRelatedFieldWidgetWrapper(RelatedFieldWidgetWrapper):
template_name = 'admin/widgets/custom_related_widget_wrapper.html'
#classmethod
def create_from_root(cls, root_widget: RelatedFieldWidgetWrapper):
# You don't need this method of you are using the MonkeyPatch method
set_attr_fields = [
"widget", "rel", "admin_site", "can_add_related", "can_change_related",
"can_delete_related", "can_view_related"
]
init_args = {field: getattr(root_widget, field) for field in set_attr_fields}
return CustomRelatedFieldWidgetWrapper(**init_args)
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
rel_opts = self.rel.model._meta
info = (rel_opts.app_label, rel_opts.model_name)
context['list_related_url'] = self.get_related_url(info, 'changelist')
return context
See, the context variable list_related_url is the relative path that we need here. Now, create an HTML file to render the output,
#File: any_registered_appname/templates/admin/widgets/custom_related_widget_wrapper.html
{% extends "admin/widgets/related_widget_wrapper.html" %}
{% block links %}
{{ block.super }}
- Link To Related Model -
{% endblock %}
How to connect?
Method-1 : Monkey Patch
# admin.py
# other imports
from ..widgets import CustomRelatedFieldWidgetWrapper
from django.contrib.admin import widgets
widgets.RelatedFieldWidgetWrapper = CustomRelatedFieldWidgetWrapper # monket patch
Method-2 : Override ModelAdmin
# admin.py
class AlbumAdmin(admin.ModelAdmin):
hyperlink_fields = ["related_field_1"]
def formfield_for_dbfield(self, db_field, request, **kwargs):
formfield = super().formfield_for_dbfield(db_field, request, **kwargs)
if db_field.name in self.hyperlink_fields:
formfield.widget = CustomRelatedFieldWidgetWrapper.create_from_root(
formfield.widget
)
return formfield
Result
There are several ways to go. Here is one.
Add some javascript that changes the existing link behavior. Add the following script at the end of the overridden admin template admin/widgets/related_widget_wrapper.html. It removes the class which triggers the modal and changes the link to the object.
It will only be triggered for id_company field. Change to your needs.
{% block javascript %}
<script>
'use strict';
{
const $ = django.jQuery;
function changeEditButton() {
const edit_btn = document.getElementById('change_id_company');
const value = edit_btn.previousElementSibling.value;
const split_link_template = edit_btn.getAttribute('data-href-template').split('?');
edit_btn.classList.remove('related-widget-wrapper-link');
edit_btn.setAttribute('href', split_link_template[0].replace('__fk__', value));
};
$(document).ready(function() {
changeEditButton();
$('body').on('change', '#id_company', function(e) {
changeEditButton();
});
});
}
</script>
{% endblock %}
This code can also be modified to be triggered for all edit buttons and not only for the company edit button.

How to fill QuerySelectField based on Other QuerySelectField's selected value in Flask

I created a WTForm. In that form two QuerySelectField are used for State Selection and District selection. I want to fill the District QuerySelectField based on State QuerySelectField's value. The following form i used in the project. Please guide me to do.
def district_choices():
return District.query.all()
def state_choices():
return State.query.all()
class CreateForm(FlaskForm):
code = StringField('Code', validators=[DataRequired()])
name = StringField('Name',validators=[validators.required(), validators.Length(max=32)])
state = QuerySelectField('State',query_factory=state_choices, get_label='name', validators=[validators.required()])
district = QuerySelectField('District',query_factory=district_choices, get_label='name', validators=[validators.required()])
When you populate the district select field, based on the previous selection, are you doing a redirect to a new page or is this to all happen on the same page? If so, JavaScript will need to be used.
For example. My solution looked like this...
In the jinja file
<head>
<script type="text/javascript" src="https://code.jquery.com/jquery-1.7.1.min.js"></script>
<script>
function makeChange() {
var new_selection= $("#week_range_date").val();
window.location.href = "/time_expenses/"+new_selection
}
</script>
</head>
{{ week_start_date_form.csrf_token }}
<div onchange="makeChange()">
Select Week to View   {{ week_start_date_form.week_range_date }}
</div>
And then you need a route to catch your redirect from JavaScript like this.
#app.route("/time_expenses/<week_start_date>", methods=['GET', 'POST'])
def time_expenses(week_start_date):
And then send your parameter value to a form like this
# create form for date range drop down
WeekStartDateForm = inititalizeWeekStartDateForm(week_start_date)
week_start_date_form = WeekStartDateForm()
And in your forms file you can do something like this
def inititalizeWeekStartDateForm(default_value):
# do any logic here in order to create your values needed for choices and default value, you can then use them inside the class below
class WeekStartDateForm(FlaskForm):
week_range_date = SelectField('Select Date Range', choices=created_dates, default=default_value)
return WeekStartDateForm
Edit:
In case it wasn't clear.
Above when I said "# do any logic here"
You need to do something like this
created_dates = []
table_objects = TableName.query.filter_by(condition_here="value_here").all()
for table_object in table_objects:
created_dates.append(table_object.column_name)
^^Doing this you can grab all the values you want for a given column in a table and throw them into a list and use them as your choices for your dropdown for your SELECT FIELD.
Then do something like
created_dates[0] for your default value, or some other determination, or just don't provide a default value and it will automatically be the first one in the list.

Accessing Attributes of a CheckboxSelectMultiple checkbox

Short Version
In the Django template language, how do I access the attributes of a given checkbox within a CheckboxSelectMultiple widget?
Long Version
The attributes of a typical Django widget can be accessed easily:
{% for field in form %}
{{ field.widget.attrs.something }}
{% endfor %}
However, this method isn't working for a checkbox within a CheckboxSelectMultiple widget.
I have a customized CheckboxSelectMultiple widget which I'm using to display a ManyToMany ModelForm field. The customized widget adds additional attributes to each checkbox in the create_option method.
The additional attributes display appropriately within the HTML of the input element:
<input type="checkbox" name="questions" value="22" id="id_questions_12" category="Category Name" category_number="3" question="Question Name" question_number="4">
I need to access these additional attributes for purposes of display and organizing the form fields.
I turned back to this after letting it sit for a week or so. After playing around some more and reading into the docs for BoundField (and BoundWidget specifically), I found out how to access the attrs of an individual checkbox in a CheckboxSelectMultiple widget:
{% for field in form %}
{% for check in field.subwidgets %}
{% for a in check.data.attrs %}
I was able to use the same technique given in this answer. It works perfectly for CheckboxSelectMultiple although it is not used in the answer.
I saved this in my project's forms.py:
from django.forms.widgets import CheckboxSelectMultiple, Select
class SelectWithAttrs(Select):
"""
Select With Option Attributes:
Subclass of Django's Select widget that allows attributes in options,
e.g. disabled="disabled", title="help text", class="some classes",
style="background: color;", etc.
Pass a dict instead of a string for its label:
choices = [ ('value_1', 'label_1'),
...
('value_k', {'label': 'label_k', 'foo': 'bar', ...}),
... ]
The option k will be rendered as:
<option value="value_k" foo="bar" ...>label_k</option>
"""
def create_option(self, name, value, label, selected, index,
subindex=None, attrs=None):
if isinstance(label, dict):
opt_attrs = label.copy()
label = opt_attrs.pop('label')
else:
opt_attrs = {}
option_dict = super().create_option(
name, value, label, selected, index,
subindex=subindex, attrs=attrs)
for key, val in opt_attrs.items():
option_dict['attrs'][key] = val
return option_dict
class CheckboxSelectMultipleWithAttrs(
SelectWithAttrs, CheckboxSelectMultiple):
pass
Here is a working snippet from a project of mine that uses this example. The stuff in the beginning isn't really important, but it shows how to build your attributes dict and pass it into your choices.
from django import forms
from django.whatever import other_stuff
from project_folder import forms as project_forms
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ['employees']
def __init__(self, *args, **kwargs)
super().__init__(*args, **kwargs)
self.fields['employees'].queryset =\
self.company.employee_set.filter(is_active=True)
existing_crews_employees = []
for crew in existing_job_crews:
crew_employees =\
[employee.__str__() for employee in crew.employees.all()]
existing_crews_employees.append({'crew_name': crew.crewtype.name,
'employees': crew_employees})
employees_choices = []
for (index, choice) in enumerate(self.fields['employees'].choices):
# loop over each choice and set proper paramaters for employees
# that are unassigned/on the current crew/on a different crew
employee_in_crew = False
employee_name = choice[1]
for crew_and_employees in existing_crews_employees:
for employee in crew_and_employees['employees']:
if employee_name == employee.__str__():
crew_name = crew_and_employees['crew_name']
if self.obj and self.obj.crewtype.name == crew_name:
# check the box if employee in current crew
employees_choices.append((choice[0], {
'label': choice[1],
'checked': True,
'id': f'id_employees_{choice[0].instance.id}'
}))
else:
# disable the choice if employee in another crew
employees_choices.append((choice[0], {
'label':
employee_name + f" (on Crew: {crew_name})",
'disabled': True}))
employee_in_crew = True
# for valid entries, ensure that we pass the proper ID
# so that clicking the label will also check the box
if not employee_in_crew:
employees_choices.append((choice[0], {
'label': choice[1],
'id': f'id_employees_{choice[0].instance.id}'}))
self.fields['employees'].widget = project_forms.CheckboxSelectMultipleWithAttrs(
choices=employees_choices)
There are two important things to keep in mind when using this technique:
Ensure that you pass the id into your attrs for your clickable options, otherwise your labels will not check the proper boxes when they are clicked.
This method currently requires initial values to be set using the new attributes dict. Ensure that you pass the 'checked': True key-value pair to any boxes that should be checked.

Django - Trouble calling function in templates and return rendering the value of that function to the same page

I have a page that renders a list of objects from my database. I need to press a button that queries my database for a specific object, does some stuff in my views, and return a price value that I would like displayed on that same page in a pop-up bubble without reloading it, in place of the filler text I have. I've tried doing this by creating a form with a button that has the object's id in the value field, and sending that value to my original page's view, and then I call a function that handles the thing I want to do with that id. I request the object's id, pass it to the new view function , and query my database and try to return the value. I make an if request.POST statement in original page's view t, and change the rendered variable to the new value I want displayed. When I press the button, I get the same filler text in my pop-up bubble, and in my console I get the following error:
ValueError: The view engine.views.search_list didn't return an HttpResponse object. It returned None instead.
So it seems that I have to return a HTTP request whenever I call a view function. I've tried returning a rendered response in my new view function, and and after my check if request.POST and passed the new value as context:
return render(request, 'original_page.html', {'value':new_value})
I get the same error. How can I return the value I want, on the same page without getting this error? I've tried using HttpResponse with a blank value, and redirect('some_place.html') to no success. I do have an Iframe which stops my html page from reloading. Here is the code I'm using:
HTML
<iframe name="submit-frame" style="display: none"></iframe>
<form action="{% url 'search_list' %}" method="post" target="submit-frame">{% csrf_token %}
<button name="productId" value="{{product.id}}" data-toggle="popover" data-animation="true" data-content="{{price}}" type="" id='button'>Reveal Price</button>
</form>
<script type="text/javascript">
$('[data-toggle="popover"]').popover({
container: 'body',
delay: { "show": 100, "fade": 100 },
})
</script>
Views - Function to get new value
def get_price(request):
product_id = request.POST.get('productId')
item = get_object_or_404(Product, id=product_id)
price = item.price
return price
Views - Original Rendered View
def search_list(request):
results = Product.objects.all().order_by('name')
price = 'no_price'
if request.POST:
print(request.POST.get('productId'))
tcg_price = get_price(request)
return render(request, 'search_result_list.html', {'tcg_price': tcg_price})
else: ...
return render(request, 'search_result_list.html', {'price': price, 'results':results})
What you are trying to do should typically be handled via an asynchronous call (AJAX). The way you currently have things set up, the form gets submitted, and the page will reload. This isn't a user-friendly experience, and is the "Web 1.0" way of doing things (also, iframes ... yuck!). Here's how I would change your setup:
New View
def ajax_get_price(request):
response = {}
product_id = request.POST.get('productId')
try:
item = Product.objects.get(id=product_id)
response['price'] = item.price
response['success'] = True
except ObjectDoesNotExist:
response['error'] = "Item not found!"
response['success'] = False
return JSONResponse(response)
New Front-end Handler
You would send data to this new "view" through an AJAX call in JavaScript. I'll use jQuery here as an example:
$("button[name='productId']").on("click", function() {
var productID = $(this).val();
$.ajax({
'data': {
'productId': productID
},
'type': "POST",
'url': "{% url 'myproject:ajax_get_price' %}"
})
.done(function(json) {
if(json.success == true) {
var itemPrice = json.price;
// TODO: Do something with the price here (show a popup, tooltip, whatever)
} else {
if(json.hasOwnProperty('error') {
// TODO: Show the error
})
}
})
.fail(function(xhr, status, error) {
// TODO: Handle this error case also
});
});
There are a few things you would need to handle above and beyond this stub:
You will likely have to handle the CSRF (cross-site request forgery) token in a beforeSend call to $.ajax() in your JavaScript code.
You should likely check for bogus queries in your view (is the request a GET call?) and other similar edge cases.

Django: How to return model formset in ajax and use in template

I need to dynamically add forms to my formset during runtime using ajax, for which I am referring to Dynamically adding a form to a Django formset with Ajax
I have multiple formsets on the same page with different prefixes.
My models are designed like so:
A user can have many phones. A phone can have many lines (if details are needed)
Accessing Many to Many "through" relation fields in Formsets
Once a user adds a new phone, I save the phone using ajax. The view is as follows
def addUserPhone(request, customer_id, location_id, user_id, **kwargs):
error_msg = u"No POST data sent."
context = {}
if request.is_ajax():
if request.method == "POST":
user = End_User.objects.get(id=user_id)
phone_client = PartialPhone_ClientForm(request.POST, prefix='new_client')
instance = phone_client.save()
#associate user to a phone
instance.end_user.add(user)
#Creating an empty lineFormset for a phone
LineFormSet = modelformset_factory(Line, form=Line_Form, can_delete=True)
client_lines = LineFormSet(queryset=Line.objects.none(), prefix='phone_client_'+str(instance.id))
# how to return the two objects instance and client_lines back to the template??
#format = 'json'
#mimetype = 'application/javascript'
#data = serializers.serialize(format, [instance])
#return HttpResponse(data)
#can we return as a context?? this gives me only a string "phoneline_set" in the template
context['phone'] = instance
context['line_set'] = client_lines
return HttpResponse(context)
else:
error_msg = u"Insufficient POST data (need 'Name ' and 'Telephone Number'!)"
else:
error_msg = "Non Ajax"
return HttpResponseServerError(error_msg)
What is the best way to now return the phone instance, and LineFormSet back to the view for rendering in the template??
If I just return a context, my view gets only string "phoneline_set". But I want to do something like
$.post("addUserPhone/",phoneData,function(data){
$('.scroll').append("<h2> {{ line_set }} </h2>")
});
If I serialize using Json and pass how can I pass the LineFormSet and use it in template?
Currently if I try to serialize my client_lines formset I get the error
AttributeError: 'LineFormFormSet' object has no attribute '_meta'
Any help is appreciated, Thanks!!
Just elaborating on Daniel's answer as requested in the comment.
Django is an MVC style framework. Models are used in order to store and access data. In Django controllers are called views, which have a job of getting a request from a user with a certain URL, get some data which might be associated with the url, and then push that data throught some tempalte which will use the data view gave it in order to fill in the placeholders inside of the template.
Here is a simple example which explains all the aspects. Imagine that there is a web site which has a database of books. So your model would store information relevant to each book - Title, Author, ISBN number, etc.
# models.py
class Book(models.Model):
title = models.CharField(max_length=64)
author = models.CharField(max_length=64)
isbn = models.CharField(max_length=64)
Now you want to add a URL example.com/book/<id>/ which will display all of the information about the book with specified id. For that to happen, couple of things need to happen. First Django controller has to catch the url with this pattern. You specify the url pattern in the urls.py file.
# urls.py
urlpattern('',
url(r'^book/(?P<id>\d+)/$', views.book),
)
Since urls.py specify a mapping between url patterns and views, that tells Django that whenever user goes to a URL with the specified pattern, Django has to give the request to the view book which will know what to do. Additionally Django will pass the book id to the view.
# views.py
def book(request, id):
# get the book
book = get_object_or_404(Book, pk=id)
context = {
'book': book
}
return render_to_response('book_template.html', context)
So inside of the view, given the ID of the book, it uses models in order to look up the book from the database, and it case it is not found, it returns 404 error to the user. Then it populates a dictionary which I called context with some values which it will pass to the template. The job of the template is to take this context dictionary and use values inside of it in order to fill in some placeholders inside the template.
# book_template.html
<html>
<head>...</head>
<body>
<h1>{{ book.title }}</h1>
<p>Author: {{ book.author }}</p>
<p>ISBN: {{ book.isbn }}</p>
</body>
</html>
So the template will take the context from the view and then use the book inside of the context in order to fill in the values inside {{ }}.
In your case you are trying to return a context to the user which does not make much sense. What you have to do is create a template which will take the that context { 'phone': instance, 'line_set': client_lines } and according to it, will render some HTML which will be returned to the user. And that HTML you can pull using AJAX and then use it however you need it.
Hopefully this clarifies some concepts for you.
Django documentation is excellent so I would recomment to also read the intro. It will explain all of the syntax and some of the shortcuts I have used in this answer (render_to_response, etc).
You don't send the context as the Ajax response, you send a rendered template fragment using that context. The template should just be the HTML containing the form that you want to insert into your div.