I've been trying to figure out if it's possible to store a variable in a Django database field. Here is an example:
class Message(models.Model):
message = models.TextField()
And then in the HTML form field, someone inputs something like this:
Hi {{ user.first_name }}, thanks for signing up to our {{ company.name }} newsletter.
That then gets saved to the database, and when an email goes out, those fields are automatically populated with the appropriate data.
Hope this makes sense. Thanks.
This is sort of a solution, it's not storing the variable in the Model..
But you can render plain strings with the template engine.. but you need to somehow fill the context / pass the user object + company - so this is halfway solution
from django.template import Template, Context
from django.core.mail import send_mail
# Fetch Message Obj
msgObj = Message.objects.all().first()
print(msgObj.message)
# Hi {{ user.first_name }}, thanks for signing up to our {{ company.name }} newsletter.
# I added a user field to message, just for ease in this example
print(msgObj.user)
# Set msg Contents as a Template
emailtemplate = Template(msgObj.message)
# The hard part, set the context.
data = {
'user': msgObj.user,
}
if 'company' in msgObj.message: # If, so we're not fetching data that's not needed (a regex like ~ '{{ keywork.[a-zA-Z0-8]+ }}' would be better)
# company in message, get user's company
data['company'] = msgObj.user.company_set.first()
# Use Template Engine to Render with Context
emailtext = emailtemplate.render(Context(data))
print(emailtext)
# Hi Neal, thanks for signing up to our TestCompany newsletter.
send_mail(
'Test Message', # subject
emailtext, # contents
None, # from
['test#example.com'], # to
fail_silently=False,
)
Per the context: You can use:
A combination of hardcoded commonly used keywords or Objects
Use extra fields in the message Obj to fetch other data (like company)
You could pass the context to the function when the mail is being sent (like the user)
Hopefully you find some of this useful. It was interesting to look into, test and learn. Ty for the question!
It's an obvious use for a models.JSONfield. Store/ retrieve/ update
instance.variables = { variable1_name: variable_1 value, ... }
and you can fill out a template such as "hello {first_name}" with
try:
template.format( **instance.variables )
except KeyError as e:
# one or more {var} in the template string didn't have a definition
# in instance.variables
print( e.args ) # is a tuple of these undefined key names.
Related
I'm trying to build unsubscribe link in my email template but problem is i'm using seperate function in my utilites.py file to render my template and don't have access to request. This function is called by schedular in backend.
I tried request.build_absolute_uri and other things but not able create the absulute link
templates
<body>
<section class="tour section-wrapper container" id="notify-form">
<table id='datatable'></table>
{{ content|safe }}
{# Unsubscribe#}
{# Unsubscribe#}
Unsubscribe
</section> <!-- /.tour </body> -->
commented code is also what i tried
tried using Sites framework but that gives doamin as example.com not what I expected
utility method
def send_notification(dataframe, email):
subject = 'That’s your subject'
from_email = 'xxxx#gmail.com' # 'from#example.com'
text_content = 'That’s your plain text.'
subscriber_email = QueryDetails.objects.get(email=email)
domain = Site.objects.get_current().domain
html_content = get_template('mail_template.html').render({'content': dataframe.to_html(classes=["table-bordered", "table-striped", "table-hover"]),'sub_email': subscriber_email, 'domain': domain})
Expected out put is if in local domain will be http://127.0.0.1/unsub/?email=xxxx#gmail.com
if in production then http://whateverproductiondomain.com/unsub/?email=xxxx#gmail.com
But if i run the program with one of commented code in template them url generated is /unsub/email=xxxx#gmail.com
and with Sites framework it's http://example.com/unsub/?email=xxxx#gmail.com
any guesses how to do it , send_notification is not getting called from views so can't pass request into it.
I did it with SITE_URL='http://127.0.0.1/' in settings.py (you have different settings in production and in development).
So you need to pass it to the template:
from app.settings import SITE_URL
def send_notification(dataframe, email):
subject = 'That’s your subject'
from_email = 'xxxx#gmail.com' # 'from#example.com'
text_content = 'That’s your plain text.'
subscriber_email = QueryDetails.objects.get(email=email)
domain = SITE_URL
html_content = get_template('mail_template.html').render({'content': dataframe.to_html(classes=["table-bordered", "table-striped", "table-hover"]),'sub_email': subscriber_email, 'domain': domain})
And in template:
Unsubscribe
To get it dynamically, you can use request.get_host().
For example, in a views function, you can pass it to the context using context['host'] = request.get_host() and call the variable in templates using {{ host }}.
When I run it locally, I get 'localhost:8000' and when in production, I get e,g., 'domain.com'. You can then treat it as a string to append or prepend that URL with whatever you need to make it useful (for example, to add http:// in front or /whatever/path in the back).
I prefer this way instead of an alternative, like hard-coding the domain name in an environment variable and calling it because that's kind more messy to manage.
Pass it through view function
domain = request.META["HTTP_HOST"]
Django version: 3.2.9 & 4.0.4
Python version:3.8.10
OS: Ubuntu 20.04lts
I have a sitetree that is using a context variable in the title. When going to that path and loading the template with {% sitetree_page_title from menu %} it returns sequence item 1: expected str instance, LazyTitle found.
I didn't find any information on this exact issue, but it seems that when using context data in the title a LazyTitle object is generated. The object is not being converted to a string so the template engine is bombing.
I will include the relevant code below. I was able to get around the issue by editing sitetreeapp.py and wrapping the return of get_current_page_title() in str(), but that feels excessive.
The app in question is a small test app I'm working to layout an example CRUD app with the features our company needs. It does nothing fancy. The app isn't 100% complete yet so if anything looks out of place, it could be I haven't got to that point, but this portion should be working fine.
The sitetree in question is loaded dynamically via the config.ready method. Menu items without context variables are working without issue. I have verified that the context variable in question is available within the template. Hopefully it's something simple that I am overlooking. Any input is appreciated. I should also note that, while I have used StackOverflow for many years, I haven't posted much so please forgive my post formatting.
sitetree.py - note insert and list are working fine
from sitetree.utils import tree, item
sitetrees = [[
# setup the base tree
tree('app_model_test', items=[
# Then define items and their children with `item` function.
item('App Test', 'test_app:app_test_home',
hint='Test CRUD App',
children=[
item('Search Results', 'test_app:app_test_list',
in_menu=False, in_sitetree=False),
item('Insert Record', 'test_app:app_test_insert',
hint="Insert a new record",
),
item('Update Record {{ object.ssn }}', 'test_app:app_test_update object.id',
in_menu=False, in_sitetree=False),
])
]),
], ]
urls.py
app_name = 'test_app'
urlpatterns = [
path('insert/', views.app_test_form, name='app_test_insert'),
path('update/<int:id>/', views.app_test_form, name='app_test_update'),
path('list/', views.appTestListView.as_view(), name='app_test_list'),
]
views.py - relevant pieces
def app_test_form(request, action='add', id=0):
if id > 0:
test_obj = get_object_or_404(appTestModel, id=id)
form = appTestForm(instance=test_obj)
if request.method == 'GET':
if id == 0:
form = appTestForm()
test_obj = None
else:
if id == 0:
form = appTestForm(request.POST)
if form.is_valid():
form.save()
return redirect('/list')
# this will have to be somewhere else and contain whatever app apps are
# installed to get all the menu entries
menus = ['app_model_test']
context = {'form': form,
'title': 'app Test',
'action': action,
'menus': menus,
'object': test_obj}
return render(request, 'CRUD_base.html', context)
dyn_tree_register.py
from sitetree.sitetreeapp import register_dynamic_trees, compose_dynamic_tree
from sitetree.utils import tree, item
from . import sitetree as app_test_tree
register_dynamic_trees(
[
compose_dynamic_tree(*app_test_tree.sitetrees),
],
# Line below tells sitetree to drop and recreate cache, so that all newly registered
# dynamic trees are rendered immediately.
reset_cache=True
)
apps.py
from django.apps import AppConfig
class AppModelTestConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'app_model_test'
def ready(self):
from . import dyn_tree_register
and lastly the relevant template portion:
{% load sitetree %}
{% for menu in menus %}
<h1 class="title" id="page-title">{% sitetree_page_title from menu %}</h1>
{% endfor %}
As I mentioned, I can make this work by modifying sitetreeapp.py and wrapping the return of get_current_page_title in str():
def get_current_page_title(self, tree_alias: str, context: Context) -> str:
"""Returns resolved from sitetree title for current page.
:param tree_alias:
:param context:
"""
return str(self.get_current_page_attr('title_resolved', tree_alias, context))
Edit - 2022-04-13
As a temporary workaround I created a wrapper template tag that calls the sitetree_page_title and then renders it and wraps the output in str(). This seems hackish to me so I appreciate any insight. Short term this will get me by but I would rather not put something like this into production as I feel there's a bug on my side rather than within sitetree otherwise more people would have run into this.
custom tag and template.Node class
#register.tag()
def app_str_sitetree_page_title(parser, token):
'''
There is an issue causing sitetree's page title function to return a LazyPageTitle object which is not being converted to a string.
This function is a temporary fix until the issue is resolved to force the return to a string.
'''
from sitetree.templatetags import sitetree
ret = sitetree.sitetree_page_title(parser, token)
return StrSitetreePageTitle(ret)
class StrSitetreePageTitle(template.Node):
'''
This is the render wrapper to ensure a string is returned from sitetree_page_title
Like app_str_sitetree_page_title this is temporary
'''
def __init__(self, title_obj) -> None:
self.title_obj = title_obj
def render(self, context):
title_render = self.title_obj.render(context)
return str(title_render)
I need to extract the messages and field.
For Example, I have this django form error result
<ul class="errorlist">
<li>__all__
<ul class="errorlist nonfield">
<li>Pointofsale with this Official receipt and Company already exists.</li>
</ul>
</li>
</ul>
from the output of this code
def post_sale(request):
sale_form = request["data"]
if sale_form.is_valid():
save_form.save()
else:
print save_form.errors
But what i need to achieve is to get the message without the tags, so i could just return those message in plain string/text.
def post_sale(request):
sale_form = request["data"]
if sale_form.is_valid():
save_form.save()
else:
# This is just pseudo code
for field in save_form.errors:
field = str(field["field"})
message = str(field["error_message"])
print "Sale Error Detail"
print field
print message
error_message = { 'field':field,'message':message }
error_messages.append(error_message )
The output would be:
Sale Error Detail
(the field where form error exists)
Pointofsale with this Official receipt and Company already exists.
Explored Questions and Documentations
displaying django form error messages instead of just the field name
Getting a list of errors in a Django form
django form errors. get the error without any html tags
How do I display the Django '__all__' form errors in the template?
https://docs.djangoproject.com/en/1.10/ref/forms/api/
https://docs.djangoproject.com/en/1.10/topics/forms/
Thanks, please tell if something is amiss or something needs clarification so i could fix it.
The errors property of a bound form will contain all errors raised by that form, as a dictionary. The key is a field or other special values (such as __all__), and the value is a list of one or more errors.
Here is a simple example on how this works:
>>> from django import forms
>>> class MyForm(forms.Form):
... name = forms.CharField()
... email = forms.EmailField()
...
>>> f = MyForm() # Note, this is an unbound form
>>> f.is_valid()
False
>>> f.errors # No errors
{}
>>> f = MyForm({}) # Now, the form is bound (to an empty dictionary)
>>> f.is_valid()
False
>>> f.errors # dictionary of errors
{'name': [u'This field is required.'], 'email': [u'This field is required.']}
In your view, depending on what you want you can just return the value of form.errors, or parse it to whatever structure your need.
for field, errors in form.errors.items():
print('Field: {} Errors: {}'.format(field, ','.join(errors))
For the specific error you have mentioned, it is a custom error raised as a result of overriding the clean() method - which is why it is listed under the special identifier __all__ and not under a specific field.
This is mentioned in the forms reference, under validation:
Note that any errors raised by your Form.clean() override will not be
associated with any field in particular. They go into a special
“field” (called __all__), which you can access via the
non_field_errors() method if you need to. If you want to attach errors
to a specific field in the form, you need to call add_error().
I use a form in template,and I want to redirect a friendly url when user fill the form
I search many issue in stackoverflow,I think my problem like this issue: Django form redirect,
but I can't understand the template tag,so I can't solve my problem.
I write the form:
<form method="GET" action="/stock/search">
search:<input class="search" name="search" type="text" value="" id="serach" >
<input type="submit" value="Go"/>
</form>
and write the urls:
url(r'^(?P<number>\w+)/$', 'stock.views.stocknumber'),
url(r'^search/$', 'stock.views.search'),
and the views:
def stocknumber(request, number):
"""
stock info
"""
stock = Stock.objects.filter(number="%s"%number)
stock_number = Stock.objects.filter(number="%s"%number)
stock_reportinfo = Reportinfo.objects.filter(number="%s"%number)
stock_blockinfo = Blockinfo.objects.filter(number="%s"%number)
stock_stockinfo = Stockinfo.objects.filter(number="%s"%number)
data = Stockhq.objects.filter(number="%s"%number).values('timeStamps','openData','highData','lowData', 'closeData', 'volData').reverse()
datalist=[ ]
for item in data:
d =[item['timeStamps'].toordinal(),item['openData'],item['highData'],item['lowData'],item['closeData'],item['volData']]
datalist.append(d)
hisdata = datalist
return render_to_response(
'stock/stock.html',
{"stock_number": stock_number,
"stock_reportinfo": stock_reportinfo,
"stock_blockinfo": stock_blockinfo,
"stock_stockinfo": stock_stockinfo,
"hisdata":simplejson.dumps(hisdata) ,
},
context_instance=RequestContext(request))
def search(request):
return HttpResponseRedirect('/stock/%s/'%request.GET['search'])
and now I hope the user input the stock number and redriect to the friendly url like:http://..../stock/YHOO
and this url is get the stock info to render template,is this idea is right?
if it is ,what is the correct code ?
I don't think that question is really similar to your question, if I'm understanding correctly. Because that one already predetermines the action of the form, the redirected URL doesn't seem to depend on the user input. So I'm not entirely sure what your question is.
Are you implementing a simple search that, when a user inputs the stock's number (I'm assuming you consider "YHOO" a number? Is the "number" input something like "YHOO" or is it actually a number?), the stock with the number (so "YHOO" stock) is displayed with all of its information on a new page with the URL "http://.../stock/YHOO"?
In any case, here are some observations.
stock = Stock.objects.filter(number="%s"%number)
stock_number = Stock.objects.filter(number="%s"%number)
Is there a reason why you have both when you don't use the first one? In addition, are numbers unique? If so, you can just do Stock.objects.get(number=number), because:
You would use "get" instead of "filter" because you'd expect a
single record to match that.
Number is already a string when
passed in, so you don't need to do "%s" % number.
You can also try the following instead of using the actual URL, to make it more Django-like.
from django.core.urlresolvers import reverse
...
def search(request):
number = request.GET['search']
return HttpResponseRedirect(reverse('stock.views.stocknumber', args=(number,)))
As I am an impressed reader of Stack Overflow I want to ask my first question here. Since I encountered a problem with a snippet and I do not know whether I made a mistake or it's a bug in the code I'm using.
I adapted this code for my own site:
http://blog.tkbe.org/archive/django-admin-search-functionality/
It works fine and it's really a great snippet.
But if my search query has length 2, I think that the results are not correct.
So for example if I search for "re" in first name and last name, I get the following results:
Mr. Tom Krem
Ms. Su Ker
Which is pretty strange. For queries with length > 2 I do not encounter this problem.
So maybe this post read somebody who is using the snippet above and can tell me whether he/she encounters the same problem.
If nobody else encounters the problem I know at least that I have a bug somewhere in my code. Maybe in the form I'm using, or something is messed up in the request context.
How can I solve this problem?
Edit 1:
The inclusion tag:
from django import template
from crm.views import SEARCH_VAR
def my_search_form(context):
return {
'context': context,
'search_var': SEARCH_VAR
}
register = template.Library()
register.inclusion_tag('custom_utilities/my_search_form.html')(my_search_form)
The my_search_form.html:
<div id="toolbar"><form
id="changelist-search"
action=""
method="get">
<div><!-- DIV needed for valid HTML -->
<label
for="searchbar"><img src="{{ context.media_url }}/crm/img/search.png"
class="icon"
alt="Search" /></label>
<input
type="text"
size="40"
name="{{ search_var }}"
value="{{ context.query }}"
id="searchbar" />
<input type="submit" value="Search" />
</div>
</form>
</div>
<script
type="text/javascript">document.getElementById("searchbar").focus();
</script>
The view:
#login_required
def crm_contacts(request):
query = request.GET.get('q', '')
#pass additional params to the SortHeaders function
#the additional params will be part of the header <a href...>
#e.g. use it for pagination / use it to provide the query string
additional_params_dict = {'q': query}
foundContacts = search_contact(request,query)
sort_headers = SortHeaders(request, LIST_HEADERS, default_order_field=1, additional_params=additional_params_dict)
if foundContacts is not None:
contact_list = foundContacts.order_by(sort_headers.get_order_by())
else:
contact_list = Contact.objects.order_by(sort_headers.get_order_by())
context = {
'contact_list' : contact_list,
'headers': list(sort_headers.headers()),
'query' : query,
}
return render_to_response("crm/contact_list.html", context,
context_instance=RequestContext(request))
The contact search form:
#models
from crm.models import Contact
from django.db.models import Q
'''
A search form from
http://blog.tkbe.org/archive/django-admin-search-functionality/
adapted to search for contacts.
'''
def search_contact(request,terms=None):
if terms is None:
return Contact.objects.all()
query = Contact.objects
for term in terms:
query = query.filter(
Q(first_name__icontains=term)
| Q(last_name__icontains=term))
return query
Another edit:
I'm using this snippet to sort the table. Probably one should know this in order to understand the code posted above.
Since I can not post links (spam protection) I will try to explain where to find it. Go to Google. Type in: django snippet table sort
Then it should be the second hit. Sort table headers. snippet nr. 308.
Edit: Add the SortHeaders() function
ORDER_VAR = 'o'
ORDER_TYPE_VAR = 'ot'
class SortHeaders:
"""
Handles generation of an argument for the Django ORM's
``order_by`` method and generation of table headers which reflect
the currently selected sort, based on defined table headers with
matching sort criteria.
Based in part on the Django Admin application's ``ChangeList``
functionality.
"""
def __init__(self, request, headers, default_order_field=None,
default_order_type='asc', additional_params=None):
"""
request
The request currently being processed - the current sort
order field and type are determined based on GET
parameters.
headers
A list of two-tuples of header text and matching ordering
criteria for use with the Django ORM's ``order_by``
method. A criterion of ``None`` indicates that a header
is not sortable.
default_order_field
The index of the header definition to be used for default
ordering and when an invalid or non-sortable header is
specified in GET parameters. If not specified, the index
of the first sortable header will be used.
default_order_type
The default type of ordering used - must be one of
``'asc`` or ``'desc'``.
additional_params:
Query parameters which should always appear in sort links,
specified as a dictionary mapping parameter names to
values. For example, this might contain the current page
number if you're sorting a paginated list of items.
"""
if default_order_field is None:
for i, (header, query_lookup) in enumerate(headers):
if query_lookup is not None:
default_order_field = i
break
if default_order_field is None:
raise AttributeError('No default_order_field was specified and none of the header definitions given were sortable.')
if default_order_type not in ('asc', 'desc'):
raise AttributeError('If given, default_order_type must be one of \'asc\' or \'desc\'.')
if additional_params is None: additional_params = {}
self.header_defs = headers
self.additional_params = additional_params
self.order_field, self.order_type = default_order_field, default_order_type
# Determine order field and order type for the current request
params = dict(request.GET.items())
if ORDER_VAR in params:
try:
new_order_field = int(params[ORDER_VAR])
if headers[new_order_field][1] is not None:
self.order_field = new_order_field
except (IndexError, ValueError):
pass # Use the default
if ORDER_TYPE_VAR in params and params[ORDER_TYPE_VAR] in ('asc', 'desc'):
self.order_type = params[ORDER_TYPE_VAR]
def headers(self):
"""
Generates dicts containing header and sort link details for
all defined headers.
"""
for i, (header, order_criterion) in enumerate(self.header_defs):
th_classes = []
new_order_type = 'asc'
if i == self.order_field:
th_classes.append('sorted %sending' % self.order_type)
new_order_type = {'asc': 'desc', 'desc': 'asc'}[self.order_type]
yield {
'text': header,
'sortable': order_criterion is not None,
'url': self.get_query_string({ORDER_VAR: i, ORDER_TYPE_VAR: new_order_type}),
'class_attr': (th_classes and ' class="%s"' % ' '.join(th_classes) or ''),
}
def get_query_string(self, params):
"""
Creates a query string from the given dictionary of
parameters, including any additonal parameters which should
always be present.
"""
params.update(self.additional_params)
return '?%s' % '&'.join(['%s=%s' % (param, value) \
for param, value in params.items()])
def get_order_by(self):
"""
Creates an ordering criterion based on the current order
field and order type, for use with the Django ORM's
``order_by`` method.
"""
return '%s%s' % (
self.order_type == 'desc' and '-' or '',
self.header_defs[self.order_field][1],
)
If you run manage.py shell and then:
>>> from crm.models import Contact
>>> from django.db.models import Q
>>> list=Contact.objects.filter(Q(first_name__icontains='re')|Q(last_name__icontains='re'))
>>> print list
What is the output?
Edit: Right, so if you try:
>>> list=Contact.objects.filter(Q(first_name__icontains='mot')|Q(last_name__icontains='mot'))
>>> print list
(I'm trying to narrow down on the terms that are giving you problem and I saw your last comment)
What is the output?
Edit: If both of the above queries work in the shell, something else is modifying your queryset somewhere and adding some additional criteria...
Are you sure sort_headers() is not modifying the queryset with more than just an order by clause? Could you post sort_headers() to your question?