Django Admin- Dynamic child ModelChoiceField queryset on parent ModelChoiceField - python

This question have been asked many times. I went through many of them, and still couldn't find what i am looking for.
I am trying to load child ModelChoiceField data on parent ModelChoiceField selection in Django-Admin only
My code is as follows:
class AddressForm(forms.ModelForm):
name = forms.CharField(max_length=150)
city = forms.ModelChoiceField(queryset=City.objects.all(), required=False)
class Meta:
model = Address
fields = ['name', 'country', 'city']
def __init__(self, *args, **kwargs):
if 'instance' in kwargs:
address = kwargs['instance']
self.base_fields['name'].initial = address.name
country = self.get_country(*args, **kwargs)
self.base_fields['city'].queryset = country.cities if country else City.objects.none()
super().__init__(*args, **kwargs)
but it's not working on onChange event.

Here's one I did for cars. You can adapt it by replacing Car Make with country and Car Model with city
Within the form __init__
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['car_make'].empty_label = 'Make'
self.fields['car_model'].empty_label = 'Model'
initial = kwargs.get('initial', None)
try: self.fields['car_model'].queryset = CarModel.objects.filter(car_make=initial['car_make'])
except: self.fields['car_model'].queryset = CarModel.objects.none()
Ajax view
def load_models(request):
car_make_id = request.GET.get('car_make')
car_models = CarModel.objects.filter(car_make_id=car_make_id).order_by('name')
return render(request, 'leases/partials/car_model_dropdown_list_options.html', {'car_models': car_models})
Ajax url
path('ajax/load-models/', views.load_models, name="ajax_load_models"),
Javascript (using JQuery) in template
$("#id_car_make").change(function () {
var url = $("#searchForm").attr("data-models-url"); // get the url of the `load_cities` view
var carMakeId = $(this).val(); // get the selected country ID from the HTML input
$.ajax({ // initialize an AJAX request
url: url, // set the url of the request (= localhost:8000/hr/ajax/load-cities/)
data: {
'car_make': carMakeId // add the country id to the GET parameters
},
success: function (data) { // `data` is the return of the `load_cities` view function
$("#id_car_model").html(data); // replace the contents of the city input with the data that came from the server
}
});
});

Related

Django - How to get checked objects in template checkboxes?

Im new in Django,
I'm using xhtml2pdf for rendering data objects from template to PDF file , and i want to allow users to render only checked objects by checkboxes into pdf, is there anyway to filter that in Django ?
Thanks
views.py :
def render_to_pdf(template_src, context_dict={}):
template = get_template(template_src)
html = template.render(context_dict)
result = BytesIO()
pdf = pisa.pisaDocument(BytesIO(html.encode('ISO-8859-1')), result, link_callback=link_callback)
if pdf.err:
return HttpResponse('Error')
return HttpResponse(result.getvalue(), content_type='application/pdf')
class ViewPDF(View):
#method_decorator(login_required(login_url='livraison:login_page'))
def get(self, request, *args, **kwargs):
checked_objects = [i dont know how to get them]
queryset = Livraison.objects.filter(id__in=[checked_objects]) # i want something does that
data = {
'livraisons' : queryset,
'now' : f'Livraisons-{date.today()}'
}
pdf = render_to_pdf('pdf_template.html', data)
return HttpResponse(pdf, content_type='application/pdf')
use forms to get user data.
class LivraisonSelectForm(forms.Form):
livraison = forms.ModelChoiceField(label='select', queryset=Livraison.objects.all(), widget=forms.CheckboxInput())
then use it in your view:
class ViewPDF(FormView):
form_class = LivraisonSelectForm
template_name = 'path_to_template_with_html_to_filter_data'
#method_decorator(login_required(login_url='livraison:login_page'))
def form_valid(self, form):
checked_objects = form.cleaned_data.get('livraison', [])
queryset = Livraison.objects.filter(id__in=checked_objects) # i want something does that
data = {
'livraisons' : queryset,
'now' : f'Livraisons-{date.today()}'
}
pdf = render_to_pdf('pdf_template.html', data)
return HttpResponse(pdf, content_type='application/pdf')
and don't forget to show this form in your html {{ form }}
**
UPDATE
**
I Found a solution , i used Ajax code to collect every selected object
// check selected objects (#pdf is the id of the button which gonna generate the pdf file)
$('#pdf').click(function () {
var id = [];
$(':checkbox:checked').each(function (i) {
id[i] = $(this).val()
})
if (id.length === 0) {
alert('Please Select something..');
} else {
$.ajax({
url: '.',
method: 'GET',
data: {
id,
},
success: function (response) {
console.log('PDF is ready')
}
})
}
});
then i ad the variable into the view function to collect the selected objects and filter the queryset
myFunction.selected_ones = request.GET.getlist('id[]')
then filter queryset in the generated_pdf_func
queryset = MODEL.objects.filter(id__in=[x for x in myFunction.selected_ones if x != '0'])
NOTE : <if x != '0'> is important in case you want to select all because it returns 0 and you don't have any id = 0 in the DB

Django testing: post request with a Form with paramters

I want to test that my view redirects after I send it correct data. The problem is that I'm using a form that needs parameters while initializing. I have a method that builds me a response that I later use in the tests. The method works for my other views but I can't make it work with views that use Forms that need parameters to initialize them.
In my forms.py I have:
class SupportIssueForm(forms.ModelForm):
class Meta:
model = SupportIssue
fields = ('user', 'property', 'title', 'text', 'is_urgent', 'is_service')
def __init__(self, person_company, properties, is_property, *args, **kwargs):
super(SupportIssueForm, self).__init__(*args, **kwargs)
self.fields['is_service'].widget.attrs['class'] = 'custom-control-input'
...
My method for generating the response I'm later using in my test looks like this:
def generate_logged_in_user_post_response(self, data):
request = self.factory.post(self.url, data={**self.form_class_args[0], **data})
request.user = self.logged_in_user
return new_support_issue_view(request)
I also tried this:
def generate_logged_in_user_post_response(self, data):
form = SupportIssueForm(**self.form_class_args[0], data=data)
request = self.factory.post(self.url, form)
request.user = self.logged_in_user
return new_support_issue_view(request)
the self.form_class_args[0] is some dictionary I declare elsewhere:
{
"person_company": person_company, # <query object>
"properties": properties, # <query object>
'is_property': False
}

Queryset return response customize

I'm pretty new to Django restframework, what i'm trying now is to return object with foreignkey.
class User(models.Model):
name = models.CharField(max_length=255,blank=True)
date_created = models.DateTimeField(auto_now_add=True)
date_modiefied = models.DateTimeField(auto_now=True)
area = models.CharField(max_length=255,blank=True)
uuid = models.CharField(max_length=255)
home = models.CharField(max_length=255,blank=True)
work = models.CharField(max_length=255,blank=True)
mobileNo = models.CharField(max_length=255,blank=True)
email = models.CharField(max_length=255,blank=True)
appVersionCode = models.CharField(max_length=255,blank=True)
photoUrl = models.CharField(max_length=255,blank=True)
serverTime = models.CharField(max_length=255,blank=True)
fcmTokenId = models.CharField(max_length=255,blank=True)
def __str__(self):
return self.name
class LocationData(models.Model):
user = models.ForeignKey(
User, related_name='user', on_delete=models.DO_NOTHING)
source_id = models.CharField(max_length=255)
latitude = models.CharField(max_length=255)
longitude = models.CharField(max_length=255)
speed = models.CharField(max_length=255)
kms = models.CharField(max_length=255)
date_created = models.DateTimeField(auto_now=True)
date_modiefied = models.DateTimeField(auto
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
class LocationDataSerializer(serializers.ModelSerializer):
class Meta:
model = LocationData
fields = '__all__'
depth = 1
I'm using def get_queryset(self):
class SyncIndexLastDataViewSet(viewsets.ModelViewSet):
serializer_class = LocationDataSerializer
def get_queryset(self):
userid = self.request.query_params.get('user_id', None)
userExist = User.objects.filter(id=userid)
if userExist.exists():
# call the original 'list' to get the original response
queryset = LocationData.objects.values('source_id').filter(user__id=userid).order_by('-source_id')[:1]
lastSourceId = queryset[0]['source_id']
response = {"collection": {"data": lastSourceId,"statusCode": status.HTTP_200_OK,"version":"1.0"}}
json = JSONRenderer().render(response)
# customize the response data
if response is not None:
return json
else:
# return response with this custom representation
response = {"collection": {"data": "","statusCode":status.HTTP_404_NOT_FOUND,"version":"1.0","error":"Not found"}}
return response
Right now the result is inside the response is below and immediately it throws this error
But i want that queryset to return as below one, Hence i can read those key-pair values in android
{ "collection": {
"data": {
"id": 31,
"source_id": "55",
"latitude": "24654",
"longitude": "454654",
"date_created": "2019-02-08T17:10:09.318644Z",
"date_modiefied": "2019-02-08T17:10:09.318714Z",
"area": "54546",
"user": {
"id": 1,
"name": "Dormy",
"date_created": "1992-01-18T03:29:53.388000Z",
"date_modiefied": "2018-02-19T05:17:00.164000Z",
"serverTime": "",
"fcmTokenId": ""
}
},
"statusCode": 200,
"version": "1.0"
}
Now the error throws
AttributeError: Got AttributeError when attempting to get a value for field source_id on serializer LocationDataSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the int instance.
Original exception text was: 'int' object has no attribute 'source_id'.
Thanks!
The answer to this depends on what type of view you are using but the bottom line is you don't do this in get_queryset you do this in the method for the type of reguest.
For example if you are using a RetrieveAPIView you should override the retrieve method from the RetrieveModelMixin like so:
class MyAPIView(RetrieveAPIView):
queryset = MyModel.objects.all()
serializer_class = MySerializer
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
data = {
"collection": {
"data": serializer.data
},
"statusCode": 200,
"version": "1.0"
}
return Response(data)
If you are using something else like a ListAPIView then you want to see what is used by that in the relevant method and override that to wrap your data.
The main thing to realise here is that it has nothing to do with getting the queryset - which is just about getting data from the database. This is about transforming the data into the correct format when sending back a response. As a result the work should be done at the point the response is made.
There are couple of solution possible for this problem. NDevox already mention how we can overwrite our retrive function and get our expected response. But If we want this will be done with every response for every api end-point and if we go this way we need to overwrite every function then its quite burden and its DRY we should avoid this as possible. One of the possible way to introduce a middleware or overwrite Response so that we can get our generic response for every-api end-point without explicitly overwrite every functionality.
Possible Solution One
As we are using DRF here we can add our own return responses with various media types, say for application/json.
First We need to add in our settings.py
REST_FRAMEWORK = {
...
'DEFAULT_RENDERER_CLASSES': (
'app_name.renderers.ApiRenderer', # our own render middleware
),
...
}
And in our custom render middleware
from rest_framework.renderers import BaseRenderer
from rest_framework.utils import json
class ApiRenderer(BaseRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
our_response_dict = {
'version': '1.0'
'data': {},
'message': '',
}
if data.get('data'):
our_response_dict['data'] = data.get('data')
if data.get('status'):
our_response_dict['statusCode'] = data.get('status')
if data.get('message'):
our_response_dict['message'] = data.get('message')
data = our_response_dict
return json.dumps(data)
Reference Link
Possible Solution Two
If we are using ModelViewset then there is another way we can achievement that. Say Our Views.py are like following
class A(serializer.ModelSerializer):
........
class B(serializer.ModelSerializer):
........
class C(serializer.ModelSerializer):
........
Our goal is to overwrite ModelViewset's to_representation function and return our custom result. This will like as following
from collections import OrderedDict
class OurParentViewset(serializer.ModelSerializer):
......
def to_representation(self, instance):
data = super(serializers.ModelSerializer, self).to_representation(instance)
result = OrderedDict()
result['data'] = data
result['version'] = '1.0'
result['statusCode'] = '2xx' # i am not fully sure how to customize this
return result
class A(OurParentViewset):
........
class B(OurParentViewset):
........
class C(OurParentViewset):
........
Implementing a custom renderer here seems to be a ways to go.
You can have requests from your android client include in the Accept header a way to identify the client to the renderer. 1 e.g.
Accept: application/json; android=true
Then compose a renderer using the JSONRenderer class to provide the format for your Android client.
# ./formatters/android_format.py
from rest_framework.renderers import JSONRenderer, BaseRenderer
from django.http.multipartparser import parse_header
class AndroidV1FormatRenderer(BaseRenderer):
media_type = 'application/json'
format = 'json'
json_renderer = JSONRenderer()
def android(self, accepted_media_type):
base_media_type, params = parse_header(accepted_media_type.encode('ascii'))
return 'android' in params
def render(self, data, accepted_media_type=None, renderer_context=None):
response = renderer_context['response']
android = self.android(accepted_media_type)
if android:
data = {
"collection": {"data": data},
"statusCode": response.status_code,
"version": "1.0"
}
return json_renderer.render(
wrapped_data, accepted_media_type, renderer_context)
This can then be used where you require response formatted that way using renderer_classes attribute of your APIView. 2
Since get_queryset won't allow you to customize the response data. I decide to take the query value that's important to me.
http://localhost/api/users/?user_id=1 --> changed into ...api/users/1
def retrieve(self, request, *args, **kwargs):
""" userid = self.request.query_params.get('user_id', None) """
userid = kwargs.get('pk')
userExist = User.objects.filter(id=userid)
if userExist.exists():
# call the original 'list' to get the original response
queryset = LocationData.objects.values('source_id').filter(user__id=userid).order_by('-source_id')[:1]
lastSourceId = queryset[0]['source_id']
response = {"collection": {"data": lastSourceId,"statusCode": status.HTTP_200_OK,"version":"1.0"}}
# customize the response data
if response is not None:
return Response(response)
else:
# return response with this custom representation
response = {"collection": {"data": "","statusCode":status.HTTP_404_NOT_FOUND,"version":"1.0","error":"Not found"}}
return response

Python/Django form where user selects from a list of Images

I'm new to django and want to make a form that allows a user to select one of three images using radio boxes, once the user selects the image it is saved to their profile and displayed on their profile page.
I am using:
django 1.8.3
userena 1.4.1
Any help or links to documentation that will help would be great.
Basic example. Models:
def get_upload(instance, filename):
return "uploaded_files/user_%s_%s_%s" % (instance.user, datetime.datetime.today().strftime("%d-%m-%Y %H-%M-%S"), filename)
class UserModel():
# your fields
image = models.FileField(upload_to=get_upload, default='')
FileField
Forms:
class UploadForm(forms.ModelForm):
"""Auxiliary class to make file uploading more convenient."""
def __init__(self, *args, **kwargs):
super(UploadForm, self).__init__(*args, **kwargs)
class Meta:
model = UserModel
fields = ('image')
View:
def upload(request):
if request.method == "POST":
profile = request.user.profile
image_type = request.POST.get("image_type", None)
if image_type == "blah":
profile.image = request.FILES[image_type]
else:
return HttpResponse("Error")
request.user.profile.save()
return HttpResponse('OK')
request.FILES
JS with soem Jquery:
var SIZE_RESTRICT = 10*1024*1024; //10 Mbytes
$(document).ready(function()
{
$(".upload_file").find(".upload_button").click(send_file);
$(".upload_file").find(".upload_file_form").find("input[type='file']").click(enable_upload);
});
function send_file()
{
if ($(this).attr("enabled") != "true") return;
// Prevent double-triple clicks with multiple upload.
$(this).attr("enabled", "false");
var form = $(this).parent().find(".upload_file_form").get(0);
var formData = new FormData(form);
var file = $(form).find("input[type='file']").get(0).files[0];
// Not sure about this
// Prevent attack with HUGE files, that will smash server memory
// TODO: Restrict file types (Ex. MIME-image, pdf, doc)
if (file.size > SIZE_RESTRICT)
{
alert("File is too big.");
return;
}
formData.append("proof_type", $(this).attr("upload-type"));
var xhr = new XMLHttpRequest();
var that = this;
// TODO: Define another events like error, abort
xhr.upload.onprogress = function(e) {
// TODO: Progressbar as e.loaded / e.total
if (e.lengthComputable)
console.log((e.loaded / e.total) * 100);
else
console.log("Cant count length");
};
xhr.onload = function(e){
// TODO: Show success confirmation to user.
if (this.response == "Success")
{
// pass
alert(this.response);
}
else if (this.response == "Error")
{
// pass
alert(this.response);
}
else
{
// pass
}
};
xhr.open('POST', '/upload_proof');
xhr.send(formData);
}
function enable_upload()
{
$(this).parent().parent().find(".upload_button").attr("enabled", "true");
}
Actually another simple example could be founded in docs
If the set of images is small and fixed, the best option is to use the choice attribute in the field that defines the image within your model.
The image field could be the path to the image file on the file system.
class UserProfile(models.Model):
GOOD = 'Good.jpg'
UGLY = 'Ugly.jpg'
BAD = 'Bad.jpg'
AVATAR_CHOICES = (
(GOOD, 'Good'),
(UGLY, 'Ugly'),
(BAD, 'Bad'),
)
avatar_img = models.CharField(max_length=255,
choices=AVATAR_CHOICES,
default=GOOD)
Another option is to use FilePathField as your model field
FilePathField(path="/path/to/avatar_images", match="*.jpg", recursive=False)
Another way is to fill the form field dynamically when the form is instantiated.
Please see this SO QA for more on that.
However, as Django docs say
But if you find yourself hacking choices to be dynamic, you’re
probably better off using a proper database table with a ForeignKey
To specify Radiobuttons to be used for the form field, please refer to the official docs on how to set the proper widget.

Update the Queryset of a Django-Select2 AutoModelSelect2Field

I can't figure out how to update the queryset of a AutoModelSelect2Field dynamically. I'm getting really strange results. For example, sometimes the select2 box will return the correct, filtered results, and sometimes it will return NO results when I enter the same characters.
my code:
#views.py
form = MyForm()
#forms.py
class MyField(AutoModelSelect2Field):
search_fields = ['name__icontains']
max_results = 10
class MyForm(forms.Form):
my_field = MyField(
queryset=project.objects.none(),
required=True,
widget=AutoHeavySelect2Widget(
select2_options={
'width': '100%',
}
)
)
def __init__(self, *args, **kwargs):
qs = kwargs.pop('queryset')
self.base_fields['my_field'].queryset = qs
super(MyForm, self).__init__(*args, **kwargs)
#self.fields['my_field'].queryset = qs
#self.base_fields['my_field'].queryset = qs
A few of the things I've tried -
update from the view:
#views.py
form = MyForm()
form.base_fields['my_field'].queryset = new_qs
and:
form = MyForm()
form.fields['my_field'].queryset = new_qs
pass the qs to the form:
#views.py
form = MyForm(queryset=Project.objects.filter(project_type=pt))
# see above code for forms.py
I've also tried setting the initial qs to all objects:
class MyForm(forms.Form):
my_field = MyField(
queryset=project.objects,
...
But I get the same problem, 90% of the time I get the results of the initial queryset, rather than the filtered objects based on the new qs.
We were able to find a pretty straightforward way to get the dropdown options to filter by additional fields (ie. first select country and then have the state dropdown only showing states from the selected country)
It was inspired by a suggestion from here (where we also posted this solution):
https://github.com/applegrew/django-select2/issues/22
in forms.py:
class StateChoices(AutoModelSelect2Field):
queryset = State.objects
def get_results(self, request, term, page, context):
country = request.GET.get('country', '')
states = State.objects.filter(country=country, name__istartswith=term)
s2_results = [(s.id, s.name, {}) for s in states]
return ('nil', False, s2_results)
the form field:
# only include this when you would have a screen where the country
# is preset and would not change, but you want to use it as a search context
country = forms.ModelChoiceField(queryset=Country.objects.all(),
widget=forms.HiddenInput())
state = StateChoices(widget = AutoHeavySelect2Widget(
select2_options = {
'minimumInputLength': 1,
'ajax': {
'dataType': 'json',
'quietMillis': 100,
'data': JSFunctionInContext('s2_state_param_gen'),
'results': JSFunctionInContext('django_select2.process_results'),
}
}))
in our javascript:
function s2_state_param_gen(term, page) {
var proxFunc = $.proxy(django_select2.get_url_params, this);
results = proxFunc(term, page, 's2_condition_param_gen');
results.country = $('#id_country').val();
return results;
}

Categories

Resources