Readonly form field in edit view - Flask-Admin - python

I tried to set a form field as readonly just for the edit view in a Flask-Admin application. Following the answers to this question I know that if I have this
class MyView(ModelView):
form_widget_args = {
'title': {
'readonly': True
}
}
I can set a form field as readonly, but this is applied to both create and edit views. How can I apply an argument to the edit view only?

A pure python solution could be using the on_form_prefill callback function that admin provides, and it's only run in the edit view. You wouldn't need form_widget_args in that case.
You could have something like this to edit the form dynamically, making that field read only:
class MyView(ModelView):
def on_form_prefill(self, form, id):
form.title.render_kw = {'readonly': True}

I would do a little workaround for this. Create a custom edit template and add it to your class. The standard edit template you find in the flask repository on github (standard edit template)
class MyView(ModelView):
edit_template = 'my_custom_edit_template.html'
And in your custom edit template make a javascript function which disables your element. So it's only disabled in edit view and not in the create view.
{% block tail %}
{{ super() }}
{{ lib.form_js() }}
<script>
window.onload = function () {
document.getElementById("myfield_id").disabled = true;
}
</script>
{% endblock %}
It's maybe not the best solution but it works for me. I think it should also be possible to change the jinja2 template to directly disable the filed. I tried: {{ form.myfield_id(disabled=True) }} but then it renders my field twice... but my first approach works.

Another way to do it is to use form_edit_rules and form_create_rules. Display the fields you only want in create view, and add the field you want to be displayed in edit view and make it read-only
form_create_rules = (
'col1', 'col2', 'col3')
form_edit_rules = (
'col1', 'col2', 'col3', 'col4')
form_widget_args = {
'col4': {
'disabled': True
}
}

I think you can do it by checking the value you are trying to edit in the method that handles edit.
For eg:
You are trying to edit a value with id 1, then in the views py file you can check for the that value and add the code.
def editdata(id=None):
if id:
form.title.render_kw = {'readonly': True}
#rest of the code

Related

Django-taggit tag value retrieval and formatting failing

I am trying to implement a tagging process for profiles so you can add your hobbies for example.
I have chosen django-taggit as it seemed quite simple and does what I need it to, plus don't really know how to do it myself from scratch.
I have managed to make it work to some extent but I am having issues with 3 things:
Not really sure what's the best way to control the form field for these tags as I generate the form automatically with widget adjustments in meta function of the form, but it might work fine after resolving the below two issues.
When there is no data for the field hobbies (tags) the field gets populated with a single tag of value "[]" as per below image.
When I add a tag of "music" and submit the form after I reload the page I get this "[]" as per image. I assumed this will be dealt with by the library, but I cannot see another similar scenario online.
When I try adding another tag of "games" and save and reload, the below happens. The initial value gets wrapped again.
My model is:
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
hobbies = TaggableManager()
My form is:
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ['hobbies',]
def __init__(self, *args, **kwargs):
super(UserProfileForm, self).__init__(*args,**kwargs)
self.fields['hobbies'].widget = forms.TextInput()
self.fields['hobbies'].widget.attrs['data-role'] = "tagsinput"
self.fields['hobbies'].widget.attrs['class'] = "form-control"
self.fields['hobbies'].required = False
My view function is:
if request.method == 'POST':
user_profile = UserProfile.objects.get(user=request.user)
form = UserProfileForm(request.POST, instance=user_profile)
print(form)
if form.is_valid():
obj = form.save(commit=False)
obj.user = request.user
obj.save()
print("Form valid")
form.save_m2m()
Using:
<script src="/static/js/tagsinput.js"></script>
<link rel="stylesheet" href="{% static 'css/tagsinput.css' %}" />
I had this exact same problem.
One solution is to apply the data-role="tagsinput" AFTER you turn a list of tags into a comma-separated string for the form.
Here is that solution:
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
def __init__(self, **kwargs):
self.fields['tags'].widget.attrs['value'] = ", ".join(list(self.instance.tags.names()))
self.fields['tags'].widget.attrs['data-role'] = "tagsinput"
Output:
As you can see, there's a problem with quotes appearing around tags that are multi-word. It also causes new tags with quotes to be saved to the database.
If double-quotes didn't appear around multi-word phrases, this would be the most elegant solution. If someone solves this in the future, drop a note!
My template is this:
<div class="m-3 p-3 border">
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-primary" type="submit">Save Form</button>
</form>
</div>
I know I can use a template tag to strip the extra quotes from the tag field itself, but then I'd have to go through and create all the form fields manually just to set the tags template tag.
For the time being, my solution is to simply use Javascript and just modify the Meta widgets section of the form.
FINAL ANSWER (for now):
forms.py
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
widgets = {
'tags': forms.TextInput(attrs={
"data-role": "tagsinput",
})
}
custom.js - put this script on the page that loads the form.
document.addEventListener("DOMContentLoaded", function(event) {
let tags_input = document.querySelector('#id_tags');
let tags_input_value = tags_input.value;
let new_value = [...tags_input_value.matchAll(/<Tag:\s*([\w\s]+)>/g)].map(([, m]) => m).join(', ')
tags_input.setAttribute('value', new_value);
}
So all we're doing is modifying the front-end presentation, and leaving all the backend internal forms functionality untouched.
So after quite a few (hundreds) of tests, I finally narrowed down where the issue was and tried to go around it with successful result.
It seems the data got amended into tag objects through tagsinput library I was using. Only when the "data-role" was specified as "tagsinput" in the forms.py the data would already come to html side as those objects and be shown incorrectly. So instead I wanted to keep the data clean and only apply data-role='tagsinput' in the end for visual aspect, which I did using:
var hobbiesTags = document.getElementById("id_hobbies");
if(hobbiesTags){
var att = document.createAttribute("data-role");
att.value = "tagsinput";
hobbiesTags.setAttributeNode(att);
};
And that resulted in the below. Maybe there are better ways to do this, I'm not sure, but it's a pretty clean solution. Share your alternatives.

How to access functions from ModelAdmin in template?

Lets assume a ConactAdmin for showing a ConactModel:
class ContactAdmin(admin.ModelAdmin):
change_form_template = "admin/contact_change.html"
def my_custom_fnc():
return "Test"
Now I want to access my_custom_fnc() in an view html my contact_change template like <p>{{my_custom_fnc()}}</p>
Is there a list of properties which I can access in my own template? I couldn't find it in the documentation.
The current modeladmin instance is available in the context as adminform.model_admin so this should work (nb : no parens - this is the Django template language, not Python) :
<p>{{ adminform.model_admin.my_custom_fnc }}</p>
This being said, depending on what my_custom_func is supposed to do, there might be better solutions...
NB : to find out what you have in a template context, you can use the {% debug %} template tag. Also, Django is OSS so you can just read the source code (which is what I did here).

How to use chart with a Wagtail CMS

I need to include custom charts to a streamfield function, as you can find with Tauchart.
But how is it possible to a user to modify the data from the wagtail admin? I have absolutely no idea on how I can do this.
I'm pondering about a form but it's possible to add a form to a streamfield ? I think yes, I can do it with a link to an existing form. But that seems hard to make and not a good idea.
What do you think about?
What is the best practice for you ?
Do you have any examples of Taucharts integration into a CMS?
You could make a custom block inside blocks.py, for example LineChartBlock() that references a template called line_chart.html. You'll need to import your custom block into your models.py file to add it to your StreamField. Then a user on the CMS admin side can enter values and save them as parameters. Which you can then pass into the template as dynamic JavaScript variables, which you can use to create your custom chart. Here's a super rough idea...
So blocks.py would look like this:
from wagtail.core import blocks
class LineChartBlock(blocks.StructBlock):
title = blocks.CharBlock()
x = blocks.DecimalBlock()
y = blocks.DecimalBlock()
data = JSONField()
class Meta:
template = 'blocks/line_chart.html'
Your models.py:
class BlogPage(Page):
body = StreamField([
('paragraph', RichTextBlock()),
('line_chart', LineChartBlock()),
])
Your template:
<script>
const title = "{{ title }}";
const x = "{{ x }}";
const y = "{{ y }}";
const data = "{{ data }}";
makeChartWizardry = () => {
doStuff;
}
</script>
<section>
<div id="chart"></div>
</section>

Custom form fields in Django

I know how to create forms in forms.py and views.py using models.py, but to want to create a birthdate field. As everyone knows we can do this birthdate = models.DateTimeField(). However, the issue is the user would have to input their birthdate by typing YYYY-MM-DD-HH-MM-SS.
So does anyone here have an elegant way for users to input their birthday?
Thanks
I believe you are looking for a nicer DateTimeWidget, not the field itself. Secondly, unless it's really necesary, you would more likely use a DateField rather than DateTimeField to record user's birthdate ;)
here's just a one example of custom datetime input widget, but there are more:
https://github.com/asaglimbeni/django-datetime-widget
in the forms, you would do:
birthdate = forms.DateTimeField(widget= ...)
cheers
Of course. Tie a date-picker to the birthday input.
See jQuery UI's datepicker widget.
The approach is:
User picks a date from datepicker widget.
The brithday input get's automatically populated as per the selected date.
After form submission, the value in the input goes to the backend.
UPDATE:
Example:
In your template:
<form action=".">
...
{{ form.birthdate }}
...
</form>
<script>
$(function() {
$( "#id_birthdate" ).datepicker({
changeYear: true,
changeMonth: true,
});
});
</script>
The id of the birthdate input is definitely going to be id_birthdate, because that is how Django assigns ids to form fields, so that is what you need to pass in the script's function.
Also, include jqueryui.js in your project.
You could use models.DateField and custom DATE_INPUT_FORMATS if needed:
https://docs.djangoproject.com/en/dev/ref/settings/#date-input-formats

I want a button on my website that will execute a python script

I am currently making a website using django. Now I want to execute a python script from my template/view with a button on the website. It should be possible but to be honest I don't know how.
An example would be best.
Thanks for any help at all.
Well got it working now and just thought I would write my answer:
view.py:
import script as gh
def get_hostname(request):
gh.main()
return HttpResponseRedirect('/')
urls.py:
...
url(r'^get_hostname/$', 'thinco.views.get_hostname'),
...
somewhere in template:
...
<form action="/get_hostname/" method="GET">
<input type="submit" value="Liste der Thin Clients laden">
</form>
...
If you are using Django - best way, IMHO, is just to create the view that will handle your python code, and then access it at onclick event via ajax request.
yourapp/views.py
def your_python_script(request):
if request.is_ajax:
# do your stuff here
...
else:
return HttpRequest(status=400)
If you are using django also you should have jQuery, and in your template add javascript code, something like this:
$("#<your_button_id>").click( function() {
$.post("your_python_script_url", {<dict with any args you need>}, function () {
// What to do when request successfully completed
});
});
And not to forget about CRSF token if your are using it. How to handle it you can find in offical django documentation.
UPDATE
You can add csrf token to page template like:
<script>
var csrf_token = '{% csrf_token %}';
...
</script>
Next, you need to bind to the global jquery ajaxSend event, and add the token to any POST request.
$("body").bind("ajaxSend", function(elm, xhr, s) {
if (s.type == "POST") {
xhr.setRequestHeader('X-CSRF-Token', csrf_token);
}
});
Something like this should work.
Create a view function and do an #dajaxice_register decorator for it:
A silly example follows:
models.py:
class Funkyness(models.Model):
funktasm = models.CharField(max_length=128)
funfasticness = models.TextField()
urls.py:
url(r'^funk$', 'views.myFunkyView'),
views.py:
def myFunkyView(request)
render_to_request('index.htm', {'where': 'home'}, context_instance=RequestContext(request))
index.htm:
{% if where %}
You are {{ where }}
{% endif %}
When you go to http://yoursite.com/funk, you will get index.htm rendered and you will get a page that says "You are home."
Now, the dynamic part...
Write a view method as such:
from django.utils import simplejson
def getHowFunky(request, v):
html = """
<div class="my_message">
This is really funky, almost stinky...
<br />
""" + str(v) + """
</div>
"""
return simplejson.dumps({'message': html})
back in index.htm:
<script type="text/javascript>
/* first argument is return JS function, the second is the dictionary of data to sent to the python method. */
function init(){
Dajaxice.package.getHowFunky(showFunky, {'v': "Your's Truly... Fubar"});
}
function showFunky(data){
/* This is the data returned back from the AJAX (Dajaxice) call. */
document.write(data.message)
}
</script>
So, you build a python method that takes inputs and returns something. You register it with Dajaxice, you call it, passing a callback method. It runs and when it succeeds, it send the python return (possibly JSON object) to the callback method as an argument. That method then writes to the screen what it got from the Dajaxice call.
For more info on Dajaxice, go to: http://dajaxproject.com/
Props to Jorge Bastida, the sole developer of Dajax/Dajaxice!
What I could think of for an answer is to use:
Django + Dajax
Django link:
https://docs.djangoproject.com/en/1.4/
Dajax is actually ajax for django:
Visit their website and refer to their examples for quick start
http://www.dajaxproject.com/
You can create your button in django view and upon triggering your button, you can use run a python snippet, not script.
If you want to run a standalone script, you could probably check out djcelery.
http://pypi.python.org/pypi/django-celery

Categories