How to get domain in django template - django

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"]

Related

How to store variables in Django database field?

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.

Generating Flask HTML template without rendering in browser?

I am trying to generate HTML files to use for:
Email Templates, and
Generate PDF files
The problem I am facing is that I want to generate the HTML, and then push it to the applicable PDF generator and/or email API. However, the only way I know how to do it in Flask is to use render_template which, even when used without return seems to mess up the content rendered in the web-browser.
For example, my email function is:
def EmailSenderHTML(to,subject,info, date, user):
sender = {"name":"app","email":"email#email.com"}
replyTo = {"name":"app","email":"email#email.com"}
to = [{"email": to}]
html_content = render_template("ReportTemplate.html", records=info, todayDate=date, user=user)
send_smtp_email = sib_api_v3_sdk.SendSmtpEmail(to=to, reply_to=replyTo, html_content=html_content, sender=sender, subject=subject)
try:
api_response = api_instance.send_transac_email(send_smtp_email)
print(api_response)
except ApiException as e:
print("Exception when calling SMTPApi->send_transac_email: %s\n" % e)
The email generates perfectly fine, but as I mentioned above, it seemingly renders the template (which makes sense).
How do I generate the HTML without actually rendering the content?

Django-cms PlaceholderField in custom plugin, used in Placeholder, not front-end editable

I am using the 2.2 django-cms on django 1.3, as well as the 2.3 django-cms git repo on django 1.4, and I notice that if I include a PlaceholderField in my model such as this:
##books/models.py
class Book(models.Model):
...
description = cmsmodels.PlaceholderField('book_description', null=True, blank=True)
class BookCMSPluginModelItem(models.Model):
t_book = models.ForeignKey(Book)
...
class BookCMSPluginModel(CMSPlugin):
featured_books = models.ManyToManyField(BookCMSPluginModelItem)
template = models.CharField(max_length=256, choices= settings.BOOKS_TEMPLATES, default=settings.BOOKS_TEMPLATES[0]);
and make my CMS Plugin like so:
##books/cms_plugins.py
class BookCMSPlugin(CMSPluginBase):
model = BookCMSPluginModel
name = _("Books Plugin")
def render(self, context, instance, placeholder):
self.render_template = instance.template;
context['instance'] = instance;
return context
Then in my template at some point I discover I can do the following:
{% for mitem in instance.featured_books.all %}
<!-- ...... -->
<div>{% render_placeholder mitem.t_book.description %}</div>
{% endfor %}
BUT, in the front end editor, I add my new plugin, and the description field does show up as a placeholder, however when I hover over it, the outer placeholder I placed the plugin into is active, and I cannot select directly the inner placeholder. It works quite well in the admin site, as I can add content using SemanticEditor and whatever else I wish to use. In the front-end, however, there's a frustrating issue with the javascript that seems to prevent the user from adding or editing the inner placeholder.
I should note that I followed the wonderful django-cms documentation here: http://docs.django-cms.org/en/latest/extending_cms/placeholders.html, but that site only addresses the issue in the context of displaying a model in a specifically designed page, unfortunately not using a template for a custom django-cms plugin.
In this thread on GitHub one of the django-cms developers says that there is no plugin-in-plugin architecture in Django CMS 2.2.
I've tried it with the latest development branch and it is working now.
Same issue with
Django==3.1.6
django-cms==3.8.0
python_version = 3.6.9
One unrecommended workaround is to change cms source code at 1 place
Go to virtualenvs/your-project-vitualenv/lib/python3.6/site-packages/cms/models
then edit the file placeholdermodel
got to line #123 Placeholder.has_change_permission and comment the first if-return.
=> Comment line #132 if not attached_models:
=> and line #136 return user.is_superuser
Save it and restart your server.
def has_change_permission(self, user):
"""
Returns True if user has permission
to change all models attached to this placeholder.
"""
from cms.utils.permissions import get_model_permission_codename
attached_models = self._get_attached_models()
# Comment this part!
# if not attached_models:
# # technically if placeholder is not attached to anything,
# # user should not be able to change it but if is superuser
# # then we "should" allow it.
# return user.is_superuser
attached_objects = self._get_attached_objects()
for obj in attached_objects:
try:
perm = obj.has_placeholder_change_permission(user)
except AttributeError:
model = type(obj)
change_perm = get_model_permission_codename(model, 'change')
perm = user.has_perm(change_perm)
if not perm:
return False
return True

Altering one query parameter in a url (Django)

I have a search page that takes a variety of parameters. I want to create a new URL by just altering one parameter in the query. Is there an easy way to do this - something like:
# example request url
http://example.com/search?q=foo&option=bar&option2=baz&change=before
# ideal template code
{% url_with change 'after' %}
# resulting url
http://example.com/search?q=foo&option=bar&option2=baz&change=after
So this would take the request url, alter one query parameter and then return the new url. Similar to what can be achieved in Perl's Catalyst using $c->uri_with({change => 'after'}).
Or is there a better way?
[UPDATED: removed references to pagination]
I did this simple tag which doesn't require any extra libraries:
#register.simple_tag
def url_replace(request, field, value):
dict_ = request.GET.copy()
dict_[field] = value
return dict_.urlencode()
Use as:
<a href="?{% url_replace request 'param' value %}">
It wil add 'param' to your url GET string if it's not there, or replace it with the new value if it's already there.
You also need the RequestContext request instance to be provided to your template from your view. More info here:
http://lincolnloop.com/blog/2008/may/10/getting-requestcontext-your-templates/
So, write a template tag around this:
from urlparse import urlparse, urlunparse
from django.http import QueryDict
def replace_query_param(url, attr, val):
(scheme, netloc, path, params, query, fragment) = urlparse(url)
query_dict = QueryDict(query).copy()
query_dict[attr] = val
query = query_dict.urlencode()
return urlunparse((scheme, netloc, path, params, query, fragment))
For a more comprehensive solution, use Zachary Voase's URLObject 2, which is very nicely done.
Note:
The urlparse module is renamed to urllib.parse in Python 3.
I improved mpaf's solution, to get request directly from tag.
#register.simple_tag(takes_context = True)
def url_replace(context, field, value):
dict_ = context['request'].GET.copy()
dict_[field] = value
return dict_.urlencode()
This worked pretty well for me. Allows you to set any number of parameters in the URL. Works nice for a pager, while keeping the rest of the query string.
from django import template
from urlobject import URLObject
register = template.Library()
#register.simple_tag(takes_context=True)
def url_set_param(context, **kwargs):
url = URLObject(context.request.get_full_path())
path = url.path
query = url.query
for k, v in kwargs.items():
query = query.set_param(k, v)
return '{}?{}'.format(path, query)
Then in the template:
<a href="{% url_set_param page=last %}">
There are a number of template tags for modifying the query string djangosnippets.org:
http://djangosnippets.org/snippets/553/
http://djangosnippets.org/snippets/826/
http://djangosnippets.org/snippets/1243/
I would say those are the most promising looking. One point in all of them is that you must be using django.core.context_processors.request in your TEMPLATE_CONTEXT_PROCESSORS.
You can try https://github.com/dcramer/django-paging
In addition to the snippets mentioned by Mark Lavin, Here's a list of other implementations I could find for a Django template tag which modifies the current HTTP GET query string.
On djangosnippets.org:
#2237 Manipulate URL query strings using context variables using a template tag by JHsaunders
#2332 Querystring Builder - create urls with GET params by jibberia
my favorite: #2413 Yet another query string template tag by atms
#2428 Add GET parameters from current request by naktinis
On PyPI:
django-spurl by Jamie Matthews
django-urltags by Calloway Project/Corey Oordt
the add_query_param filter in django-rest-framework by Tom Christie
On GitHub:
update_querystring by David Gouldin

Django templatetag "order of processing"

I am trying to write a set of template tags that allow you to easily specify js and css files from within the template files themselves. Something along the lines of {% requires global.css %}, and later in the request, {% get_required_css %}.
I have this mostly working, but there are a couple of issues. We'll start with the 'timing' issues.
Each template tag is made up of two steps, call/init and render. Every call/init happens before any render procedure is called. In order to guarantee that all of the files are queued before the {% get_required_css %} is rendered, I need to build my list of required files in the call/init procedures themselves.
So, I need to collect all of the files into one bundle per request. The context dict is obviously the place for this, but unfortunately, the call/init doesn't have access to the context variable.
Is this making sense? Anyone see a way around this (without resorting to a hack-y global request object)?
Another possibility to store these in a local dict but they would still need to be tied to the request somehow... possibly some sort of {% start_requires %} tag? But I have no clue how to make that work either.
I've come up with a way to do this which more suits your needs. It will have a bit more load on the server, but proper caching can help to alleviate most of that. Below I've outlined a way that should work if the CSS includes are the same for each path. You'll need to create a single view to include all of these files, but you can actually optimize your CSS using this method, making only a single CSS call for each page.
import md5
class LoadCss(template.Node):
def __init__(self, tag_name, css):
self.css = css
self.tag_name = tag_name
def render(self, context):
request = context['request']
md5key = md5.new(request.path).hexdigest()
if md5key not in request.session:
request.session[md5key] = list()
## This assumes that this method is being called in the correct output order.
request.session[md5key].append(self.css)
return '<!-- Require %s -->' % self.css
def do_load_css(parser, token):
tag_name, css = token.split_contents()
return LoadCss(tag_name, key)
register.tag('requires', do_load_css)
class IncludeCss(template.Node):
def __init__(self, tag_name):
self.tag_name = tag_name
def render(self, context):
request = context['request']
md5key = md5.new(request.path).hexdigest()
return '<link rel="stylesheet" href="/path/to/css/view/%s">' % md5key
def do_include_css(parser, token):
return IncludeCss(token)
register.tag('get_required_css', do_include_css)
views.py:
from django.conf import settings
from django.views.decorators.cache import cache_page
import os
#cache_page(60 * 15) ## 15 Minute cache.
def css_view(request, md5key):
css_requires = request.session.get(md5key, list())
output = list()
for css in css_requires:
fname = os.path.join(settings.MEDIA_ROOT, 'css', css) ## Assumes MEDIA_ROOT/css/ is where the CSS files are.
f = open(fname, 'r')
output.append(f.read())
HttpResponse(''.join(output), mimetype="text/css")
This allows you to store the CSS information in the context, then in the session, and serve the output from a view (with caching to make it faster). This will, of course, have a bit more server overhead.
If you need to vary the CSS on more than just the path, then you can simply modify the md5 lines to suit your needs. You have access to the entire request object, and the context, so almost everything should be in there.
Beware: On second review, this may cause a race condition if the browser fetches the CSS before the session has been populated. I do not believe Django works that way, but I don't feel like looking it up right now.