I am wiring my framework to wagtail. It is a custom module css framework called reely.io that is made for Hubspot. It has jinja variables for breakpoints, colors, etc. one of the variables is a multiplier for padding and margin settings.
I have the css file set up and coming through to the template. the issue is with any logic in the css file:
{{ spacing_multiplier * 1 + 'px' }}
{{ break_sm + 30 + 'px' }}
Yes, this works in Hubspot's HubL, which I always considered a derivative of Jinja, but the syntax is a bit different. am I doing it wrong?
views.py
from django.views.generic import TemplateView
class Themes(TemplateView):
template_name = "css/reely.css"
content_type = "text/css"
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
''' Responsive Breaks '''
context["break_xl"] = 1773
context["break_lg"] = 1292
context["break_md"] = 768
context["break_sm"] = 576
context["break_xs"] = 396
''' Spacing Multiplier '''
context["spacing_multiplier"] = 10
''' Font '''
context['google_font_link'] = "https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght#300;400;600;700&display=swap"
context['font_family_1'] = "'IBM Plex Sans', sans-serif;"
''' Color Set 1 '''
context["primary_color"] = '#333333' '''rgb(51,51,51)'''
context["secondary_color"] = '#555555' '''rgb(85,85,85)'''
context["tertiary_color"] = '#FFFFFF' '''rgb(255,255,255)'''
''' Color Set 2 '''
context["primary_font_color"] = '#333333' '''rgb(51,51,51)'''
context["secondary_font_color"] = '#555555' '''rgb(85,85,85)'''
context["tertiary_font_color"] = '#FFFFFF' '''rgb(255,255,255)'''
''' Color Set 3 '''
context["accent_color_1"] = '#DD0000' '''rgb(221,0,0)'''
context["accent_color_2"] = '#84C318' '''rgb(132,195,24)'''
context["accent_color_3"] = '#F96900' '''rgb(249,105,0)'''
context["success_color"] = '#28A745' '''rgb(40,167,69)'''
context["info_color"] = '#17A2B8' '''rgb(23,162,184)'''
context["warning_color"] = '#FFC107' '''rgb(255,193,7)'''
context["danger_color"] = '#DC3545' '''rgb(255,193,7)'''
return self.render_to_response(context)
urls.py
from django.urls import path
from . import views
urlpatterns = [
path("config/reely/css", views.Themes.as_view(),
name="reelycss"),
]
expected output:
context["spacing_multiplier"] = 10
{{spacing multiplier * 30 +'px'}} = 10 * 3 = 30 + 'px' = '30px'
and I get that there are strings and ints here HubL doesn't have an issue with it. This doesn't work either.
context["spacing_multiplier"] = 10
{{spacing multiplier * 30 }} = 10 * 3 = 30 = 30
The answer is to use a custom template filter.
css:
{% load reely_tags %}
.ry [class*="pr3" i] {
padding-right: {{spacing_multiplier|space:3}};
box-sizing: border-box;
}
app/templatetags/reely_tags.py:
from django import template
register = template.Library()
#register.filter(name="space")
def space(value, size):
output = str(value * size)
return f"{output}px"
output css:
.ry [class*="pr3" i] {
padding-right: 30px;
box-sizing: border-box;
}
I'd be interested to know how Hubspot made HubL compared to Jinja2 now, or even what makes the difference in this type of functionality, but custom template tags are a good solution for this. I'm now able to wire the context variables for the css into my wagtail admin, which was the goal.
Related
I am attempting the solution mentioned in this stack overflow post (Adding a button to Wagtail Dashboard) however the solution might be outdated, or at least it doesn't work for me and I'm unsure why.
Goal: Be able to export a object's data to csv
First, the button HTML code had to be slightly adjusted to be formatted correctly like so:
{% extends "modeladmin/index.html" %}
{% block header_extra %}
<div class="right">
<div class="addbutton" style="margin-left: 2em;">
{% include "modeladmin/includes/button.html" with button=view.button_helper.export_button %}
My button
</div>
</div>
{{ block.super }}{% comment %}Display the original buttons {% endcomment %}
{% endblock %}
and then I copied and pasted the views and helper functions:
from django.contrib.auth.decorators import login_required
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.utils.functional import cached_property
from django.utils.translation import ugettext as _
from wagtail.contrib.modeladmin.helpers import AdminURLHelper, ButtonHelper
from wagtail.contrib.modeladmin.views import IndexView
class ExportButtonHelper(ButtonHelper):
"""
This helper constructs all the necessary attributes to create a button.
There is a lot of boilerplate just for the classnames to be right :(
"""
export_button_classnames = ['icon', 'icon-download']
def export_button(self, classnames_add=None, classnames_exclude=None):
if classnames_add is None:
classnames_add = []
if classnames_exclude is None:
classnames_exclude = []
classnames = self.export_button_classnames + classnames_add
cn = self.finalise_classname(classnames, classnames_exclude)
text = _('Export {}'.format(self.verbose_name_plural.title()))
return {
'url': self.url_helper.get_action_url('export', query_params=self.request.GET),
'label': text,
'classname': cn,
'title': text,
}
class ExportAdminURLHelper(AdminURLHelper):
"""
This helper constructs the different urls.
This is mostly just to overwrite the default behaviour
which consider any action other than 'create', 'choose_parent' and 'index'
as `object specific` and will try to add the object PK to the url
which is not what we want for the `export` option.
In addition, it appends the filters to the action.
"""
non_object_specific_actions = ('create', 'choose_parent', 'index', 'export')
def get_action_url(self, action, *args, **kwargs):
query_params = kwargs.pop('query_params', None)
url_name = self.get_action_url_name(action)
if action in self.non_object_specific_actions:
url = reverse(url_name)
else:
url = reverse(url_name, args=args, kwargs=kwargs)
if query_params:
url += '?{params}'.format(params=query_params.urlencode())
return url
def get_action_url_pattern(self, action):
if action in self.non_object_specific_actions:
return self._get_action_url_pattern(action)
return self._get_object_specific_action_url_pattern(action)
class ExportView(IndexView):
"""
A Class Based View which will generate
"""
def export_csv(self):
data = self.queryset.all()
response = ...
return response
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
super().dispatch(request, *args, **kwargs)
return self.export_csv()
class ExportModelAdminMixin(object):
"""
A mixin to add to your model admin which hooks the different helpers, the view
and register the new urls.
"""
button_helper_class = ExportButtonHelper
url_helper_class = ExportAdminURLHelper
export_view_class = ExportView
def get_admin_urls_for_registration(self):
urls = super().get_admin_urls_for_registration()
urls += (
url(
self.url_helper.get_action_url_pattern('export'),
self.export_view,
name=self.url_helper.get_action_url_name('export')
),
)
return urls
def export_view(self, request):
kwargs = {'model_admin': self}
view_class = self.export_view_class
return view_class.as_view(**kwargs)(request)
and then I added the ModelAdmin to the hooks. I'm able to see the button however it doesn't function as it's supposed to (exporting the csv). In fact, it does absolutely nothing (the href is # after all) and I feel like I'm missing some steps.
I used the same implementation as you.
I guess that you are having problems in the def export_csv(self) method
here my implementation
from djqscsv.djqscsv import render_to_csv_response
class ExportView(IndexView):
model_admin = None
def export_csv(self) -> dict:
if (self.model_admin is None) or not hasattr(
self.model_admin, "csv_export_fields"
):
data = self.queryset.all().values()
else:
data = self.queryset.all().values(*self.model_admin.csv_export_fields)
return render_to_csv_response(data)
#method_decorator(login_required)
def dispatch(self, request: HttpRequest, *args: list, **kwargs: dict) -> dict:
super().dispatch(request, *args, **kwargs)
return self.export_csv()
csv_export_fields can be added to your model admin to specify which fields you want to export
here I am adding the HTML file:
{% extends "modeladmin/index.html" %}
{% block header_extra %}
{% include 'modeladmin/includes/button.html' with button=view.button_helper.export_button %}
{{ block.super }}
{% endblock %}
your admin.py
class MyModelAdmin(ModelAdmin, ExportModelAdminMixin):
model = MyModel
menu_icon = 'tag'
menu_order = 200
index_template_name = "wagtailadmin/export_csv.html"
csv_export_fields = [ "field_name","field_name_1", ]
list_display = ('first_name', 'last_name'')
search_fields = ('first_name', 'last_name',)
I am a beginner in Django and Python. I am creating a Payroll project whose calculations are done using a function from models.py. The user must input the variables through templates and save it to sql. Then search for the employee (through templates again) and output his payroll details. That is the time when I want to use the calculation function.
Data from the database is working and is outputted by the templates. As for the calculated data, it simply isn't showing.
I have been trying to use the function to no avail and I've searched for 3 days already. I am at lost at what to do now.
models.py
from django.db import models
#Class in model.py acts as a table in database
class Add_Employee(models.Model):
name = models.CharField (max_length = 150, default = '', null = False)
position = models.CharField (max_length = 150, default = '', null = False)
email = models.EmailField (max_length = 150, default = '', null = False)
address = models.CharField (max_length = 500, default = '', null = False)
basic_pay = models.FloatField(default=None)
overtime_hours = models.IntegerField(default=None)
allowance = models.FloatField(default=None)
days_leave = models.IntegerField(default=None)
other_deductions = models.FloatField(default=None)
#Django admin page; the table will show the name
def __str__(self):
return '{}{}'.format(self.name, self.position, self.email, self.address)
def salary_calculation(self):
#Earnings
self.overtime_hours_pay = self.overtime_hours * 64
self.gross_income = self.basic_pay + self.overtime_hours + self.allowance
#Deductions
self.days_leave = self.days_leave * 512
self.pagibig = self.basic_pay * 0.01
self.gsis = self.basic_pay * 0.09
self.withholdingtax = self.basic_pay * 0.15
self.philhealth = self.basic_pay * 0.0275 / 2
self.total_deductions = self.days_leave + self.pagibig + self.gsis + self.withholdingtax + self.philhealth
#Net Pay
self.net_pay = self.gross_income - self.total_deductions
print ("Calculated.") #this was never outputted from all the times i tried.
return (self)
views.py
#Code for form submission and redirect back to employer page again
def add_employee_form_submit(request):
print ("Form is successfully submitted.") #print can only be seen on the terminal, not the browser
#create local variable for each variable entered using the form
name = request.POST["name"]
address = request.POST["address"]
email = request.POST["email"]
position = request.POST["position"]
basic_pay = request.POST["basic_pay"]
overtime_hours = request.POST["overtime_hours"]
allowance = request.POST["allowance"]
days_leave = request.POST["days_leave"]
other_deductions = request.POST["other_deductions"]
#assigning the local variable into the database fields
employee_info = Add_Employee(name = name, address = address, email = email, position = position, basic_pay = basic_pay, overtime_hours = overtime_hours, allowance = allowance, days_leave = days_leave, other_deductions = other_deductions )
#save the entire stuff
employee_info.save()
return render(request, 'employer.html')
def search_employee_form_submit(request):
if request.method == "POST":
search_id = request.POST['search_id']
if search_id:
employee_match = Add_Employee.objects.filter( pk = search_id ) #pk is primary key
if employee_match:
return render (request, 'employee.html', {'Search': employee_match})
else:
messages.error(request, 'No results found.')
else:
return HttpResponseRedirect('employee/')
return render(request, 'employee.html')
employee.html (template for query and result viewing)
<h1>Employee Payroll</h1>
<br><br>
<h2>Enter your ID:</h2>
<form action="/search_employee_form_submit/", method="post"> <!--it will use 'search_employee_form_submit' url after submitting form-->
{% csrf_token %} <!-- added for privacy reasons/security-->
<br>
<input type = "text" name="search_id" placeholder="Enter employee id">
<br><br>
<input type="submit" value="search employee">
</form>
<br><br>
<!-- Code for when your search has results / employee -->
{% if Search %}
{% for k in Search %}
Name:
{{ k.name }} <br>
Position:
{{ k.position }} <br>
Email:
{{ k.email }} <br>
..... etc (this part is working)
Overtime Hour Pay:
{{ k.overtime_hours_pay }}<br>
Gross Income:
{{ k.gross_income }}<br><br>
..... etc (calculated data = doesnt show result)
{% endfor %}
Pardon me for the long post as I have no idea where I am missing something.
First of all, you are referencing fields that do not exist in the model, such as overtime_hours_pay and gross-income. If you do self.someFieldName on a model instance, that field should be defined or inherited in the model. So this is wrong:
self.overtime_hours_pay = self.overtime_hours * 64
You could either
Add those fields in the model definition
Remove the self part and make them normal variables. Which would look like:
def salary_calculation(self):
overtime_hours_pay = self.overtime_hours * 64
gross_income = self.basic_pay + self.overtime_hours + self.allowance
self.days_leave = self.days_leave * 512
pagibig = self.basic_pay * 0.01
gsis = self.basic_pay * 0.09
withholdingtax = self.basic_pay * 0.15
philhealth = self.basic_pay * 0.0275 / 2
total_deductions = self.days_leave + pagibig + gsis + withholdingtax + philhealth
net_pay = gross_income - total_deductions
return net_pay
After doing that, you could calculate net_pay in the view.
...
if employee_match:
# calculation
net_pay = employee_match.salary_calculation()
# then pass this to the template
return render (request, 'employee.html', {'Search': employee_match, 'net_pay': net_pay})
else:
messages.error(request, 'No results found.')
I'm just learning Django, so my question might seem not worth attention, but i spent some time googling and havnt found an answer.
I have two models and a function to fill it
urls.py
url(r'^upd/$', update_database, name="upd")
views.py
def update_database(request):
grabdata()
def grabdata():
url = "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_day.geojson"
weburl = urllib.request.urlopen(url)
if (weburl.getcode() == 200):
data = json.loads(weburl.read())
EarthQuakes.objects.all().delete()
LastGetSession.objects.all().delete()
lastsession = LastGetSession(hand=data["metadata"]["title"])
lastsession.save()
for i in data["features"]:
place = i["properties"]["place"]
place = str.split(",")
place = place[-1]
time = i["properties"]["time"]
mag = i["properties"]["mag"]
rept = i["properties"]["felt"]
if rept is None:
rept = 1
longitude = 0
latitude = 0
earthquake = EarthQuakes(place=place, time=time,
mag=mag, rept=rept,
longitude=longitude, latitude=latitude)
earthquake.save()
template
<a id="update_button" class="btn btn-info btn-lg" href="{% url 'upd' %}" >Update_database</a>
But i didn't get how to execute it from a view.
Or I assume there is a way to call a conroller function that calls the model function or something like that
It returns the "The view earthquake.views.update_database didn't return an HttpResponse object. It returned None instead."
It feels like there is one missing piece or the pazzle
On Your views.py
def my_view(request):
grabdata()
return HttpResponse('Done')
in urls.py add a new url
url(r'^my-view/$', views.my_view,name='my_view')
in your template
Update
I am using Python Django to create an interactive Dashboard. The main reason why I decided to use Django is because I can create users and groups to give different access to different people. Thought this was the better way.
I have massive reports built on excel sheets that I can convert to .CSV and machine readable. I read the best way to load data to D3 is through d3.csv(). I've tried that without success and now have tried with d3.json() but still I have used another code sample to try and plot my own but could have any luck.
The code is as follows. Important note: In dashboard.html, there is a selector, thus the HTML Template has that extends the "dashboard" template.
# app/models.py
from django.db import models
class wrtes(models.Model):
name = models.CharField(max_length=150)
date = models.DateTimeField()
# app/urls.py
from django.conf.urls import url
from . import views
from .views import graph, oneModel
urlpatterns = [
# Dashboard parts
url(r'^oneModel/', graph),
url(r'^api/oneModel', oneModel, name='oneModel'),
]
# app/views.py
def graph(request):
return render(request, 'app/oneModel.html')
def oneModel(request):
data = oneModel.objects.all() \
.extra(select={'month': connections[oneModel.objects.db].ops.date.trunc_sql('month', 'date')}) \
.values('month') \
.annotate(count_items=Count('id'))
return JsonResponse(list(data), safe=False)
<!-- app/oneModel.html -->
{% extends 'app/dashboard.html' %}
{% load staticfiles %}
{% block content %}
<script src="https://d3js.org/d3.v3.js"></script> <!-- D3 obligatory tags -->
<script>
var margin = {top: 20, right: 20, bottom: 30, left: 50},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var parseDate = d3.time.format("%Y-%m-%d").parse; // for dates like "2014-01-01"
//var parseDate = d3.time.format("%Y-%m-%dT00:00:00Z").parse; // for dates like "2014-01-01T00:00:00Z"
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.x(function(d) { return x(d.month); })
.y(function(d) { return y(d.count_items); });
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.json("{% url "oneModel" %}", function(error, data) {
data.forEach(function(d) {
d.month = parseDate(d.month);
d.count_items = +d.count_items;
});
x.domain(d3.extent(data, function(d) { return d.month; }));
y.domain(d3.extent(data, function(d) { return d.count_items; }));
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Play count");
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
});
</script>
{% endblock %}
When running the local server, it does not render anything in the graphics part. It renders the remaining parts - navbar, selector & so on. It makes me consider the dataset is not being loaded in any way. This one in particular, does not even make sense :-D But when I tried with the d3.csv() method, it was not working either way.
What is wrong with the code?
What is the best way to pass .csv files to D3 to plot cute stuff? If you'll be referencing guides, please consider I am not a programmer nor a very experienced one.
Thank you for your time.
I am trying to get the fileadmin to get multiple files uploaded but can't figure out how to do that.
Currently in the below reference I can only upload one file at a time.
https://github.com/flask-admin/flask-admin/blob/master/examples/file/app.py
(New Link to to file: https://github.com/mrjoes/flask-admin/blob/master/examples/file/app.py )
I tried updating the html template to have multiple="" but that didn't helped to upload multiple files.
Further looking into this I think this html file needs to have multiple=""
Python27\Lib\site-packages\flask_admin\templates\bootstrap3\adminC:\Python27\Lib\site-packages\flask_admin\templates\bootstrap3\admin\lib.html
Although I am not sure where/how to add this tag without actually overwriting the source file.
You should custom ImageUploadInput and ImageUploadField as follows.
import ast
from PIL import Image
from wtforms.widgets import html_params, HTMLString
from wtforms.utils import unset_value
from flask_admin.helpers import get_url
from flask_admin.form.upload import ImageUploadField
from flask_admin._compat import string_types, urljoin
class MultipleImageUploadInput(object):
empty_template = "<input %(file)s multiple>"
# display multiple images in edit view of flask-admin
data_template = ("<div class='image-thumbnail'>"
" %(images)s"
"</div>"
"<input %(file)s multiple>")
def __call__(self, field, **kwargs):
kwargs.setdefault("id", field.id)
kwargs.setdefault("name", field.name)
args = {
"file": html_params(type="file", **kwargs),
}
if field.data and isinstance(field.data, string_types):
attributes = self.get_attributes(field)
args["images"] = " ".join(["<img src='{}' /><input type='checkbox' name='{}-delete'>Delete</input>"
.format(src, filename) for src, filename in attributes])
template = self.data_template
else:
template = self.empty_template
return HTMLString(template % args)
def get_attributes(self, field):
for item in ast.literal_eval(field.data):
filename = item
if field.thumbnail_size:
filename = field.thumbnail_size(filename)
if field.url_relative_path:
filename = urljoin(field.url_relative_path, filename)
yield get_url(field.endpoint, filename=filename), item
class MultipleImageUploadField(ImageUploadField):
widget = MultipleImageUploadInput()
def process(self, formdata, data=unset_value):
self.formdata = formdata # get the formdata to delete images
return super(MultipleImageUploadField, self).process(formdata, data)
def process_formdata(self, valuelist):
self.data = list()
for value in valuelist:
if self._is_uploaded_file(value):
self.data.append(value)
def populate_obj(self, obj, name):
field = getattr(obj, name, None)
if field:
filenames = ast.literal_eval(field)
for filename in filenames[:]:
if filename + "-delete" in self.formdata:
self._delete_file(filename)
filenames.remove(filename)
else:
filenames = list()
for data in self.data:
if self._is_uploaded_file(data):
self.image = Image.open(data)
filename = self.generate_name(obj, data)
filename = self._save_file(data, filename)
data.filename = filename
filenames.append(filename)
setattr(obj, name, str(filenames))
After that, You can use them as image upload example in flask-admin
class ModelHasMultipleImages(db.Model):
id = db.Column(db.Integer(), primary_key=1)
# len of str representation of filename lists may exceed the len of field
image = db.Column(db.String(128))
class ModelViewHasMultipleImages(ModelView):
def _list_thumbnail(view, context, model, name):
if not model.image:
return ''
def gen_img(filename):
return '<img src="{}">'.format(url_for('static',
filename="images/custom/" + form.thumbgen_filename(image)))
return Markup("<br />".join([gen_img(image) for image in ast.literal_eval(model.image)]))
column_formatters = {'image': _list_thumbnail}
form_extra_fields = {'image': MultipleImageUploadField("Images",
base_path="app/static/images/custom",
url_relative_path="images/custom/",
thumbnail_size=(400, 300, 1))}
The image filed of ModelHasMultipleImages store the str represent of filename list.
Dropzone is the best way to do this
Download with
pip install flask flask-uploads flask-dropzone
The Python app.py should look somthhing like this
# app.py
from flask import Flask, render_template
from flask_dropzone import Dropzone
from flask_uploads import UploadSet, configure_uploads, IMAGES, patch_request_class
import os
app = Flask(__name__)
dropzone = Dropzone(app)
# Dropzone settings
app.config['DROPZONE_UPLOAD_MULTIPLE'] = True
app.config['DROPZONE_ALLOWED_FILE_CUSTOM'] = True
app.config['DROPZONE_ALLOWED_FILE_TYPE'] = 'image/*'
app.config['DROPZONE_REDIRECT_VIEW'] = 'results'
# Uploads settings
app.config['UPLOADED_PHOTOS_DEST'] = os.getcwd() + '/uploads'
photos = UploadSet('photos', IMAGES)
configure_uploads(app, photos)
patch_request_class(app) # set maximum file size, default is 16MB
#app.route('/')
def index():
return render_template('index.html')
#app.route('/results')
def results():
return render_template('results.html')
And result.html looks as follows
# templates/results.html
<h1>Hello Results Page!</h1>
Back<p>
<ul>
{% for file_url in file_urls %}
<li><img style="height: 150px" src="{{ file_url }}"></li>
{% endfor %}
</ul>
index.html looks something like this
# templates/index.html
<!DOCTYPE html>
<html>
<head>
<title>Flask App</title>
{{ dropzone.load() }}
{{ dropzone.style('border: 2px dashed #0087F7; margin: 10%; min-height: 400px;') }}
</head>
<body>
<h1>Hello from Flask!</h1>
{{ dropzone.create(action_view='index') }}
</body>
</html>