Using Wagtail 2.9, I am trying to create a block that has a function that generates URL. To generate the URL I need the current logged in user.
class Look(blocks.StructBlock):
title = blocks.CharBlock(required=True, help_text='Add your look title')
id = blocks.CharBlock(required=True, help_text='Enter the Id')
class Meta:
template = "looker/looker_block.html"
value_class = LookStructValue
the value class has the get_url() definition which looks like:
class LookStructValue(blocks.StructValue):
def url(self):
id = self.get('id')
user = User(15,
first_name='This is where is need the current user First name',
last_name='and last name',
permissions=['some_permission'],
models=['some_model'],
group_ids=[2],
external_group_id='awesome_engineers',
user_attributes={"test": "test",
"test_count": "1"},
access_filters={})
url_path = "/embed/looks/" + id
url = URL(user,url_path, force_logout_login=True)
return "https://" + url.to_string()
Can i get the current user inside the LookStructValue class?
You can access the parent's context (parent_context) using the blocks.Structblock's get_context method.
Be sure you render the block with {% include_block %}.
The parent_context keyword argument is available when the block is rendered through an {% include_block %} tag, and is a dict of variables passed from the calling template.
You'd have to rethink how you were creating the user's URL, instead moving it to a method (example: create_custom_url) on the User model.
# Basic Example, to point you the right way.
class DemoBlock(blocks.StructBlock):
title = blocks.CharBlock()
def get_context(self, value, parent_context=None):
"""Add a user's unique URL to the block's context."""
context = super().get_context(value, parent_context=parent_context)
user = parent_context.get('request').user
context['url'] = user.create_custom_url()
return context
Related
I'm trying to implement a simple 'like' button, however, the value of the object is not changing. I have only a week of experience with django, any help is appreciated!
model>
class Mainnews(models.Model):
author = models.ForeignKey(Author, on_delete=models.DO_NOTHING, default= True)
title = models.CharField(max_length=200)
description = models.TextField()
image = models.ImageField(upload_to = 'photos/%Y/%m/%d/')
is_published = models.BooleanField(default = True)
publish_date = models.DateTimeField(default = datetime.now, blank = True)
views_counter = models.IntegerField(default=0)
likes = models.IntegerField(default=0)
def __str__(self):
return self.title
html>
<form action = "{% url 'like' %}" method = "POST">
{% csrf_token %}
<button type ='submit'>Like</button>
</form>
view>
def like(request):
if request.method == 'POST':
thelike = Mainnews.likes + 1
thelike.save()
url> on like redirect to homepage
path('news/', like, name = 'like'),
the ideal way to do it is using ajax ,so your page will not refresh every time you click the button,what missing in your question the condition of model which you want to increase ,in my code i set it news id you can change it ,check this :
<input id='news_id' name='news_id' >
<input type='button' onclick='Increase()' name='increase' >
<srcipt>
function Increase(){
var new_id= document.getElementById("news_id").value;
$.ajax({
type:"POST",
data:JSON.stringify({'data':new_id}),
success:function(responsedata){
// process on data
}
})
}
But if you want to use ajax you have to add a csrf_exempt decorator on your view
from django.views.decorators.csrf import csrf_exempt
#then write this before your view
#csrf_exempt
you can use F() object to generate a SQL expression that describes the required operation without actually having to pull them out of the database into Python memory check here you can use this :
if request.is_ajax():
new_id = json.loads(request.body.decode('utf-8'))["data"]
_reponse = Mainnews.objects.filter(id=new_id).update(likes=F('likes') + 1)
return JsonResponse({'data_to_return':'data'})
As stated in the comments by both Mahdi and Abdul, you shouldn't need a form to increase the value of Likes for a particular Mainnews instance. This can be done just by creating a URL and view specific to this purpose.
First, you will need to create a view specifically for increasing likes against a Mainnews instance. That view should look something like this:
def add_likes(request, news_id):
news_instance = Mainnews.objects.get(id=news_id)
news_instance.likes = news_instance.likes += 1
news_instance.save()
# Assuming that you want to display the same news instance again, you would need to send back the same instance to the news instance view
context = {
'news_instance': news_instance,
}
return render(request, 'your_template_here', context=context)
You will also need to create a URL path for updating likes. That should look something like this:
path('/news_item/add_like/<int:news_id>', add_likes, name='your-url-name'),
Then finally you would need to update your news template something like this (assuming you are using CSS to drive your front end):
Add Like
This will result in the page being refreshed by the user when they click on the "Like" button. If that is not the behavior you want, the answer submitted by Belhadjer would probably help prevent the refresh.
I'm using Wagtail 2.0 with a custom Block that has the following code:
class LinkButtonBlock(blocks.StructBlock):
label = blocks.CharBlock()
URL = blocks.CharBlock()
styling = blocks.ChoiceBlock(
choices=[
('btn-primary', 'Primary button'),
('btn-secondary', 'Secondary button'),
('btn-success', 'Success button'),
('btn-info', 'Info button'),
('btn-warning', 'Warning button'),
('btn-error', 'Error button'),
],
default='btn-info',
)
outline = blocks.BooleanBlock(
default=False
)
#property
def css(self):
btn_class = self.styling
if self.outline is True:
btn_class = btn_class.replace('btn-', 'btn-outline-')
return btn_class
class Meta:
icon = 'link'
template = 'testapp/blocks/link_button_block.html'
If I then try to access this css "property" in my template, nothing seems to happen. Putting a print(self) as first line inside the css def also shows nothing on the console suggesting the function never even gets called.
Using the following template:
{% load wagtailcore_tags %}
<a class="btn {{ block.value.css }}" href="{{ block.value.URL }}">{{ block.value.label }}</a>
Simply yields:
<a class="btn " href="actual.url.from.instance">actual.label.from.instance</a>
Also, block.value.styling and block.value.outline on their own work just fine, so... what am I doing wrong here?
The thing that's tripping you up is that the value objects you get when iterating over a StreamField are not instances of StructBlock. Block objects such as StructBlock and CharBlock act as converters between different data representations; they don't hold on to the data themselves. In this respect, they work a lot like Django's form field objects; for example, Django's forms.CharField and Wagtail's CharBlock both define how to render a string as a form field, and how to retrieve a string from a form submission.
Note that CharBlock works with string objects - not instances of CharBlock. Likewise, the values returned from StructBlock are not instances of StructBlock - they are a dict-like object of type StructValue, and this is what you need to subclass to implement your css property. There's an example of doing this in the docs: http://docs.wagtail.io/en/v2.0/topics/streamfield.html#custom-value-class-for-structblock. Applied to your code, this would become:
class LinkButtonValue(blocks.StructValue):
#property
def css(self):
# Note that StructValue is a dict-like object, so `styling` and `outline`
# need to be accessed as dictionary keys
btn_class = self['styling']
if self['outline'] is True:
btn_class = btn_class.replace('btn-', 'btn-outline-')
return btn_class
class LinkButtonBlock(blocks.StructBlock):
label = blocks.CharBlock()
URL = blocks.CharBlock()
styling = blocks.ChoiceBlock(choices=[...])
outline = blocks.BooleanBlock(default=False)
class Meta:
icon = 'link'
template = 'testapp/blocks/link_button_block.html'
value_class = LinkButtonValue
According to the Document model below, the 'document.upload.name' in the template will display like "user_NAME/smile.jpg".
I want to split the "user_NAME/smile.jpg" by '/'. So in the template, it will only display "smile.jpg".
I use the 'value.split' in the filter of 'upload_extras'. but the error was: 'str' object has no attribute 'filter_function'.
this is the template:
<td><a href="{{ document.upload.url }}" target="_blank">{{
document.upload.name|filename_extract }}</a></td>
this is my function in upload_extras.py
#upload_extras.py
from Django import template
register =template.Library
#register.filter('filename_extract')
def filename_extract(value):
'''extract the file name from the upload_to_path'''
folder, filename = value.split('/')
return filename
And the last is the model:
# Create your models here.
def upload_to_path(instance, filename):
return 'user_{0}/{1}'.format(instance.user.username, filename)
class Document(models.Model):
uploaded_at = models.DateTimeField(auto_now_add=True)
upload = models.FileField(upload_to=upload_to_path)
user = models.ForeignKey(User, on_delete=models.CASCADE)
You need to load your custom filter in the template.
At the top of your template, add
{% load upload_extras %}
from django import template
register = template.Library()
#register.filter('filename')
def filename(value):
folder, filename=value.split("/")
return filename
I changed the function name from 'filename_extract' to 'filename'. it is working now. this is a very wired situation.
(I am new to Django)
I'm having a problem with my Django template.
The cruise_details filter should only return one row, but when I try and display this in the template with cruise_details.port for example, nothing is displayed. "code" is correctly getting passed from the URL.
If I remove .port and just put cruise_details I am presented with this on the page
<QuerySet [<Cruise: B724>]>
view.py
def cruise(request, code):
return render(request, 'cruise.html', {
'cruise_details': Cruise.objects.filter(code=code)
})
cruise.html
{{ cruise_details.port}}
models.py
class Cruise(models.Model):
code = models.CharField(max_length=10)
destination = models.CharField(max_length=60)
url = models.URLField
ship = models.ForeignKey(Ship)
duration = models.IntegerField
start = models.DateField
end = models.DateField
added = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
port = models.CharField(max_length=30)
The issue is that Cruise.objects.filter(code=code) returns a list, so if there are many possible matches you can modify your template to show them all
{% for cruise_detail in cruise_details %}
{{ cruise_details.port }}
{% endfor %}
Alternatively, if you know there can only be one result then you can use get instead:
Cruise.objects.get(code=code)
and your existing template should work.
Hope this helps.
I have a stacked inline display. The model for the Inline class has a child in a ManyToMany relationship. I want to display the images but I cannot see how to stop django from escaping the html. It seems like I need a function similar to "display_as", but how do I get django to gather up all the available images and display them in "checkboxSelectMultiple".
FYI: I want to add some sorting to the images too after I get them to display.
Models.py
class BlogWidgetCarousel(models.Model):
entry = models.TextField()
blog = models.ForeignKey(Blog, blank=True, null=True)
position = models.PositiveSmallIntegerField("Position")
images = models.ManyToManyField("Image")
class Meta:
ordering = ('position', )
def __str__(self):
return str(self.position)
def save(self, *args, **kwargs):
self.entry = "<b><i>TODO: create image slider</i></b>"
super(BlogWidgetCarousel, self).save(*args, **kwargs)
def display(self):
return self.entry
class Image(models.Model):
title = models.CharField(max_length=60, blank=False, null=False)
image = models.ImageField(upload_to="images/")
def thumb(self):
return '<img src="{0}">'.\
format(MEDIA_URL + str(self.image))
def __str__(self):
#return self.title
#return '<img src="{0}">'.format(MEDIA_URL + str(self.image))
return mark_safe("<b>BOLD</b>") #Added just to test escaping... bold tags still appear on page.
__str__.allow_tags = True #does not appear to work
admin.py
class BlogWidgetCarouselInline(admin.StackedInline):
formfield_overrides = {
models.ManyToManyField: {'widget': CheckboxSelectMultiple},
}
model = BlogWidgetCarousel
extra = 0
#django knows images is ManyToMany
fieldsets = (
("Create Carousel:", {
'fields': (("position"), 'images',)
}),
("Result:", {
'fields': ('thumb', 'display_as',)
}),
)
readonly_fields = ('display_as', 'thumb',)
def display_as(self, instance):
return instance.display()
display_as.allow_tags = True
def thumb(self, instance):
x = ""
for i in instance.images.all():
x += i.thumb()
return x
thumb.allow_tags = True
Update:
What I found is that widget I am using has a render function with the following line:
return format_html( '<label{}>{} {}</label>',
label_for, self.tag(attrs), self.choice_label)
This means that the value the template uses is already escaped. Altering as so fixes the problem:
return format_html(
'<label{}>{} {}</label>', label_for, self.tag(attrs), mark_safe(self.choice_label)
)
Now I am not sure if I am implementing something in the "incorrect" way or if it is normal to need to write a custom widget and overwrite the render function.
You can use format_html() for it. The django.utils.html module provides some low level utilities for escaping HTML.
This function is to be preferred over string interpolation using % or str.format directly, because it applies escaping to all arguments - just like the Template system applies escaping by default.
You could have used mark_safe() to escape HTML like below:
mark_safe(u"%s <b>%s</b> %s" % (some_html,
escape(some_text),
escape(some_other_text),
))
But by using the below code,
format_html(u"{0} <b>{1}</b> {2}", mark_safe(some_html), some_text, some_other_text)
you don’t need to apply escape() to each argument and risk a bug and an XSS vulnerability if you forget one.
You can use the autoescape built-in template tag in your template.
This tag takes either on or off as an argument and that determines whether auto-escaping is in effect inside the block. The block is closed with an endautoescape ending tag.
When auto-escaping is in effect, all variable content has HTML escaping applied to it before placing the result into the output (but after any filters have been applied). This is equivalent to manually applying the escape filter to each variable.
{% autoescape on %}
{{ image_object }}
{% endautoescape %}
This should solve your problem.
I found that the way to solve this issue for me was to use a raw_id_field.