I almost have x-editable working with the django API built with tastypie thanks to various other answers on stackoverflow, but not quite.
Here is the html:
<a href="#" id="field_name" class="editable"
data-name="name"
data-pk="{{ object.id }}"
data-value="{{ object.name }}"
data-title="Meeting Name">
{{ object.name }}</a>
and the javascript:
$('.editable').on('init', function(e, edt) {
edt.options.url = '/api/v1/update_meeting/' +edt.options.pk;
});
$('.editable').editable({
mode: 'inline',
ajaxOptions: {
type: 'PATCH'
},
success: function(response, newValue) {
// nothing to do
}
});
And the tastypie resource:
class UpdateMeetingResource(ModelResource):
class Meta:
queryset = Meeting.objects.all()
resource_name = 'update_meeting'
limit = 0
include_resource_uri = False
list_allowed_methods = ['get','patch',]
detail_allowed_methods = ['get', 'patch']
serializer = urlencodeSerializer()
authentication = Authentication()
authorization = Authorization()
My only problem is that the field name gets updated with "name" and not the value in data-value. Before I put in the data-name attribute, it was setting the value to "field_name".
I could fix this by simply changing the patch-detail method in my tastypie resource, but it would be nice to get it working without doing that.
So the problem was that the patch being sent was a key value pair and that was not being recognised by tastypie, but a quick modifiation to hydrate fixes it. Maybe not the best solution but here is what is working for me:
The html:
<a href="#" id="name" class="editable editable_default_setup"
data-title="Name"
data-pk="{{ object.pk }}"
data-value="{{ object.name }}"
data-placeholder="Meeting Name" >
{{ object.name }}</a>
The javascript:
$('.editable').on('init', function(e, edt) {
edt.options.url = '/api/v1/meeting/' + {{ object.id }};
});
$.fn.editable.defaults.ajaxOptions = {type: "PATCH"};
$('.editable_default_setup').editable();
Setting up the resource:
class Meta:
queryset = Meeting.objects.all()
include_resource_uri = False
resource_name = 'meeting'
limit = 100
allowed_methods = ['post','put','get','options','patch', 'delete']
detail_allowed_methods = ['patch']
authentication = Authentication()
authorization = Authorization()
serializer = urlencodeSerializer()
always_return_data=True
The important bit, Modification to the tastypie resource:
def hydrate(self, bundle):
if bundle.request.method == "PATCH":
# data is supplied by x-editable in format {u'pk': u'1170', u'name': u'owner', u'value': u'5', u'format': u'json'}
# apply this value to the obj and return
field_name = bundle.data['name']
field_value = bundle.data['value']
# the use of name is unfortunate as it can override a field called name, so put it back to original value unless updating it
# do the same with value, just in case
bundle.data['name'] = getattr(bundle.obj, 'name', None)
bundle.data['value'] = getattr(bundle.obj, 'value', None)
# now set the attribute field_name to field_value so object will update
bundle.data[field_name] = field_value
setattr(bundle.obj, field_name, field_value)
return bundle
Related
Have poured over SO and every blog and/or video I can find. I'm new to JS/AJAX/JQUERY, and have recently tried to incorporate these guys into my Flask apps, much to my current dismay. Trying to simply submit the form data to my MySQL DB via an AJAX call, without refreshing the page. This is far as I've gotten. Don't even see the post request being made in the inspector. Maybe it's not hitting the route? Here's my code:
Form HTML
<form name="race_form" id="race_form">
<input type="text" id="characterID" value="{{ character.id }}" />
<input type="text" id="raceID" value="{{ attribute.id }}" />
<button type="submit" id="toggleoption-{{ attribute.attribute_name }}"
class="btn btn-default char_gen_options_btn" style="margin: 10px 0px 0px 10px;
background:url(/static/img/blacksmith.jpg) right center no-repeat; background-size:contain;
background-color:#B5B6AF;font-weight:900;">Select {{ attribute.attribute_name }}</button>
</form>
AJAX
$(document).ready(function() {
$('#race_form').submit(function(event) {
event.preventDefault();
var character_id = $('#characterID').val();
var attribute_id = $('#raceID').val();
$.ajax({
url: "{{ url_for('auth.register_race') }}",
type: 'POST',
data: { character_id: character_id, attribute_id: attribute_id }
});
});
});
Flask View (committing to many to many table)
#auth.route('/register_race', methods=['POST'])
#login_required
def register_race():
character = Character.query.filter_by(id=request.form['characterID']).first()
character.character_attributes.character_id = request.form['characterID']
character.character_attributes.attribute_id = request.form['raceID']
db.session.commit()
return jsonify({'result': 'success'})
And the tables from models.py that the view is referencing:
class Character(db.Model):
"""
Create a Player Character table - all characters assigned to user
"""
__tablename__ = 'characters'
id = db.Column(db.Integer, primary_key=True, unique=True)
character_name = db.Column(db.String(60), index=True, unique=True)
experience_points = db.Column(db.Integer) # Refers to proprietary character build points
character_lives = db.Column(db.Integer, default=0)
character_img = db.Column(db.String(300)) # Path to character img on server
create_date = db.Column(db.DateTime, default=func.now())
last_update = db.Column(db.DateTime, onupdate=func.now())
# Keys
users = db.relationship('User', secondary='user_character')
attribute = db.relationship('Attribute', secondary='character_attributes')
items = db.relationship('Items', secondary='inventory')
files = db.relationship('File', secondary="character_files")
def __repr__(self):
return '<Character Name: {}>'.format(self.character_name)
class CharacterAttributes(db.Model):
"""
Many to many table for characters and assigned attributes
"""
__tablename__ = 'character_attributes'
id = db.Column(db.Integer, primary_key=True, unique=True)
create_date = db.Column(db.DateTime, default=func.now())
last_update = db.Column(db.DateTime, onupdate=func.now())
amount_purchased = db.Column(db.Integer)
# Keys
character_id = db.Column(db.Integer, db.ForeignKey('characters.id'))
attribute_id = db.Column(db.Integer, db.ForeignKey('attributes.id'))
character = db.relationship('Character', backref=backref('character_attributes', cascade='all, delete-orphan'))
attribute = db.relationship('Attribute', backref=backref('character_attributes', cascade='all, delete-orphan'))
I'm new to coding and django and I'm struggling to find the solution to the following problem having reviewed the answers I've found.
Im creating a search form with multiple fields. When the user selects the first field category (and before hitting search) I would like to dynamically change the queryset for the second field sub_category such that only related values are shown.
I have models.py as follows:
class Product(models.Model):
category = models.ForeignKey("Category")
sub_category = models.ForeignKey("SubCategory")
class Category(models.Model):
name = models.CharField(max_length=256)
class SubCategory(models.Model):
category = models.ForeignKey("Category")
name = models.CharField(max_length=256)
And my forms.py includes:
class BasicSearchForm(forms.Form):
category = forms.ModelChoiceField(
label='Category',
queryset=Category.objects.all(),
to_field_name="name",
empty_label=None,
initial="Red")
sub_category = forms.ModelMultipleChoiceField(
required=False,
label='Type',
queryset= SubCategory.objects.all(),
to_field_name="name",
widget=forms.Select)
And my views.py includes:
def search(request):
if request.method == 'POST':
form = BasicSearchForm(request.POST)
if form.is_valid():
category = form.cleaned_data['category']
sub_category = form.cleaned_data['sub_category']
return render(request, 'myapp/search.html', {'form': form})
else:
form = BasicSearchForm()
return render(request, 'myapp/search.html', {'form': form})
And finally the search.html includes:
<form class="search-form" role="search" action="/search/" method="get">
{{ form }}
<input type="submit" value="Search" />
</form>
I've played around with a few answers but nothing seems to work. I'd really appreciate some help. Thanks in advance!
Update:
Thanks for the feedback. As a result I updated the following:
In my urls.py:
urlpatterns = [
url(r'^ajax/update_subcategories/$', views.update_subcategories, name='update_subcategories'),
And in my views.py:
def update_subcategories(request):
category = request.GET.get('category', None)
sub_category = list(SubCategory.objects.filter(category__name__exact=category).values('name'))
return JsonResponse(sub_category, safe=False)
And I have this in my myapp/search.html:
{% block javascript %}
<script>
$("#id_category").change(function () {
var category = $(this).val();
$.ajax({
url: '{% url "myapp:update_subcategories" %}',
data: {
'category': category,
},
success: function (response) {
var new_options = response;
alert(new_options[0].name); // works
$('#id_sub_category').empty();
$.each(new_options, function(key, value) {
$('#id_sub_category')
.append($('<option>', { value : key })
.text(value.name));
});
}
});
</script>
{% endblock %}
Update: The sub_category options were showing as [object Object] until I changed value to value.name and it looks like it's working. I'll test it out and close unless there are any comments.
Update: Im still having an issue with the browser back button. When a user clicks back the dropdown values have changed back to the original queryset rather than the updated version.
You can't do this from Django views side, ie, backend. You could try an ajax request for implementing this kind of requests, by sending a GET request to the server for populating the drop-down or whatever you are into.
For a simple example, you could refer
here
How do I POST with jQuery/Ajax in Django?
EDIT
def update_subcategories(request):
category = request.GET.get('category', None)
sub_category = list(SubCategory.objects.filter(category__name__exact=category).values('name'))
return JsonResponse(dict(sub_category=sub_category))
Then in ajax response you could grab it like response.data.sub_category
Use ajax to send the category and retrieve subcategory elements.
For the category, send it via get request, and using the orm return the subcategories in a json format which you can show using jQuery.
I have a model class which has an attribute that refers to django DB objects. I would like to change this attribute using a single view with setattr() which I use to make changes to any attributes for this object.
The problem is, I can't seem to pass an object instance through the stack. I'm not sure if I can even use setattr() for this. Actually I'm not even sure if the problem is with my attempted use of setattr() or something else - please let me know!
Error on POST attempt:
ValueError at /dollhouseupdate/1
Cannot assign "u'Citadel'": "Dollhouse.dh_background" must be a "Background" instance.
Model:
class Dollhouse(models.Model):
dollhouse_name = models.CharField(max_length=100)
user = models.ForeignKey(User)
dh_background = models.ForeignKey(Background)
def __str__(self):
return self.dollhouse_name
Template:
<select id="background-select">
<option value="null">Change Background</option>
{% for background in background_objects %}
<option value="{{ background }}">{{ background.bg_name }} </option>
{% endfor %}
</select>
View:
def dollhouseupdate(request, dollhouseid):
if request.method == 'POST':
workingdollhouse = Dollhouse.objects.get(id=dollhouseid)
if request.POST.get('erase') == "true":
workingdollhouse.delete()
return HttpResponse("Dollhouse deleted!")
else:
data = (request.POST).dict()
for key, value in data.items():
setattr(workingdollhouse, key, value)
workingdollhouse.save()
return HttpResponse("Dollhouse {} saved!".format(workingdollhouse.dollhouse_name))
Javascript:
//change dollhouse background
$("#background-select").change(function() {
if($("#background-select").val() != null) {
var dollhouseid = workingDollhouse;
var dh_background = $("#background-select").val()
console.log("changing background to " + dh_background);
$.ajax("http://127.0.0.1:8000/dollhouseupdate/"+dollhouseid, {
type: 'POST',
data: {
dh_background: dh_background,
}
})
.done(function(response){
console.log("The request is complete!");
console.log(response);
window.location = "http://127.0.0.1:8000/";
})
.fail(function() {
console.log("Sorry, there was a problem!");
})
};
});
You are passing the object id in the POST variable, not the actual object itself (you can't do it anyway). Either change the following part
data: {
dh_background: dh_background,
}
to
data: {
dh_background_id: dh_background,
}
or get the object instance using the id in your view code.
As the error says, the Dollhouse.dh_background attribute must be an instance of the Background model. You are attempting to set its value to an object of a different type; I think a text string.
type(u'Citadel') is Background # False
Instead, you'll need to put some smarts into the view so that Background instances are retrieved by whatever key you have; then, set the Dollhouse.dh_background attribute to that instance.
if name == 'background':
background_code = post_args[name]
background = Background.objects.get(code=background_code)
workingdollhouse.dh_background = background
Because different POST arguments will refer to different fields, you will need to know what each one refers to and treat them differently. A simple “setattr for each one” won't work.
I'm trying to implement a dictionary of forms where two fills are initialized with some values I have. I'm passing this through a Jquery function but when its going to add it to the template, the jquery function has an error that says Cannot set property 'value' of null when trying to execute this document.getElementById('example1').value = example;
My code here:
view.py
def exampleCaesar(request):
if request.is_ajax() and request.method == "GET":
form = caesarCipher(request.GET or None)
if form.is_valid:
wordToEncrypt = request.GET.get('word')
wordToEncrypt = wordToEncrypt.upper()
wordLength = len(request.GET.get('word'))
key = request.GET.get('the_key')
print(wordLength)
print(key)
equations = {}
for x in range(wordLength):
exampleForm = caesarCipher(initial={'letterOfWord' : wordToEncrypt[x], 'key' : key})
##print(exampleForm)
if exampleForm.is_valid:
equations = (exampleForm)
print(equations)
context = { 'equations' : equations
}
return render(request, "content/exampleCaesar.html", context)
Javascript file
$("#encryptButton").on({
click : function() {
var variable = document.getElementById('id_plaintext');
console.log(variable.value)
$.ajax( {
url: "/exampleCaesar",
type : "GET",
data: { CSRF: 'csrf_token',
word: $('#id_plaintext').val(),
the_key: $('#id_key').val()
},
success : function(example) {
$('#example1').show();
$('#example1').html(example);
document.getElementById('example1').value = example;
console.log(example);
}
}); //END OF Ajax
} //END OF FUNCTION
}); //END OF encryptButton
Template file
{% for letterOfWord, key in equations.items %}
<form onsubmit="return false;" method="GET" class="exaSubmit" enctype="multipart/form-data">
{% csrf_token %}
<div id="example1" type="hidden">
( {{ letterOfWord }} + {{ keyToUse }} ) MOD 26 =
{{ letterToFill }} <button name="action" class="validateButton" value="validate"> Validate </button> <br>
</div>
</form>
{% endfor %}
I believe I'm not filling the dictionary the right way. When I try to print it out in the console to see the values, only the names of the fields are but not the values. My bets are in this section but not entirely sure how's that possible when I'm appending the information to it.
I can't get how to fetch data from HTML-element that contains data generated by django-autocomplete-light.
Here is a code of the form:
class ThreadForm(forms.Form):
topic = forms.CharField(label="Topic", max_length=255)
body = forms.CharField(label="Body", widget=forms.Textarea(attrs={'rows': '12', 'cols':'100'}))
tags = autocomplete_light.fields.MultipleChoiceField(choices=(tuple((tag.name, tag.name) for tag in Tag.objects.all())),
label='Tags',
widget=autocomplete_light.widgets.MultipleChoiceWidget('TagAutocomplete',
attrs={'class':'form-control',
'placeholder':'Tag'}
)
)
def save(self, author, created):
topic = self.cleaned_data['topic']
body = self.cleaned_data['body']
tags = self.cleaned_data['tags']
th = Thread(author = author,
topic = topic,
body = body,
created = created,
)
rtags = []
for tag in tags:
sr = Tag.objects.get(tag)
rtags.append(sr.name)
th.save()
Tag.objects.update_tags(th, tags)
And autocomplete_light_registry.py:
from threads.models import Thread
import autocomplete_light
from tagging.models import Tag
class TagAutocomplete(autocomplete_light.AutocompleteModelBase):
search_fields = ['^name']
autocomplete_light.register(Tag, TagAutocomplete, attrs={
'data-autocomplete-minimum-characters': 1,
},)
As you see I've changed the django-autocomplete app. In the base.py I found a variable choice_html_format = '<span data-value="%s" name="choice">%s</span>'
Attribute name was added by me to get data like that:
tags = request.POST.get('name')
But this doesn't work. I'm getting an error like "NoneType in not callable"
Next thing I've tried is change choice_html from base.py:
def choice_html(self, choice):
"""
Format a choice using :py:attr:`choice_html_format`.
"""
return self.choice_html_format % (
escape(self.choice_value(choice)),
escape(self.choice_label(choice)))
It is original function, I've changed choice_value(choice) to choice_label(choice). And got an error "invalid literal for int() with base 10: <tag_name_here>". Looks like data-value attribute is only for int() type (but I can't get where I can change it, maybe in js-function, I don't know).
And the last, I'm trying to get the pk of each tag, and then get the name via manager. But I'm getting error Cannot resolve keyword '4' into field. Choices are: id, items, name.
I absolutely sure that there is an easy way to perform the task I need.
autocomplete-light has a template called widget.html that is rendered in the template:
...
{% block select %}
{# a hidden select, that contains the actual selected values #}
<select style="display:none" class="value-select" name="{{ name }}" id="{{ widget.html_id }}" multiple="multiple">
{% for value in values %}
<option value="{{ value|unlocalize }}" selected="selected">{{ value }}</option>
{% endfor %}
</select>
{% endblock %}
...
as you can see, this <select> element contains all selected choices for the autocomplete widget.
Its name (we are going to identify it by its name attribute later in the view) is simply the autocomplete's name ('tags').
So now you need to make sure your autocomplete field in the template is wrapped in <form> tags so the values get submitted (if you haven't already).
The next step is to retrieve the data in the view:
request.POST.getlist('tags')
That's it. You now have a list of primary keys of the selected values:
>>> print(str(request.POST.getlist('tags'))
['1', '3', '4', '7', ...]