My Wagtail site has certain items viewable in a gallery app, that the user can edit so that it would be also shown as an item in the shop app. It's strictly one-to-one, so I don't want them to be managing the items separately. I thought proxy models might be the best way to achieve this but I'm getting stuck and not finding very much documentation about using proxy models with Wagtail. Also possibly my regex is bad.
In app 'gallery':
class GalleryItem(Page):
parent_page_types = ['InstallationPage']
description = models.CharField(blank=True, max_length=250)
direct_sale = models.BooleanField("Direct Sale", default=False, help_text="Check this box to list this item for sale directly on your website.")
direct_sale_price = models.DecimalField("Sale price, $", blank=True, null=True, max_digits=6, decimal_places=2, help_text="Add more info about this item for the store page only.")
direct_sale_extra_description = models.CharField("Addtional sale description (optional)", blank=True, max_length=250, )
stock = models.IntegerField("Number in stock", blank=True, null=True,)
In app 'shop':
from gallery.models import GalleryImage
class Shop(Page):
def get_context(self, request):
context = super().get_context(request)
shop_items = ShopItem.objects.filter(Q(direct_sale=True) | Q(external_sale=True))
paginator = Paginator(shop_items, 24)
page = request.GET.get('page')
try:
pagin = paginator.get_page(page)
except PageNotAnInteger:
pagin = paginator.get_page(1)
context['shop_items'] = shop_items
return context
class ShopItem(GalleryItem, RoutablePageMixin):
class Meta:
proxy = True
parent_page_types = ['Shop']
#route(r"^shop/(?P<item_slug>[-\w]*)/$", name="item_view")
def item_view(self, request, item_slug):
# ????? Trying different things here:
context = self.get_context(request)
try:
item = ShopItem.objects.get(slug=item_slug)
except Exception:
item = None
if item is None:
# 404
pass
context["item"] = item
return render(request, "shop/shop_item.html", context)
The gallery item can be viewed at /gallery/item-slug. I would like to also view that item with a different template at /shop/item-slug, however I'm only able to achieve a 404 page.
RoutablePageMixin doesn't work in the way you're imagining. For it to work, the page has to exist in the page tree and be findable through a normal path-based URL; any routes defined within it are then handled relative to that path. So, if you have a RoutablePageMixin page at /foo/bar/baz/ that defines a view method with for #route(r'^shop/([-\w]*)/$'), that method will respond to URLs like /foo/bar/baz/shop/item-slug/. Since your tree doesn't have any pages that actually identify themselves as ShopItem, the route defined there will never be reached.
You don't really need a ShopItem model at all - all you're doing is responding to subpaths of /shop/, which is presumably the URL of your Shop page, so you can make Shop the RoutablePageMixin page instead:
from django.shortcuts import get_object_or_404
class Shop(RoutablePageMixin, Page):
def get_context(self, request):
# ... logic for the main /shop/ page, as before
#route(r'^([-\w]+)/$') # view method for subpaths of /shop/
def item_view(self, request, item_slug):
item = get_object_or_404(GalleryItem, slug=item_slug)
return render(request, "shop/shop_item.html", {'item': item})
Related
I'm fairly new to Django and am working on making user profile pages accessible by using the user's username in the url, e.g. mysite.com/profile/someusername
I'll be having links to the profile in a couple places, but the first place I'm experimenting on is in my navbar to access the logged-in user's page.
base.html
<a class="dropdown-item" href="{% url 'fillups:user_profile' username=user.username %}" class="btn btn-simple">Overview</a>
This correctly displays the currently logged-in user's name, for the case of this example we'll user the username seconduser
This is the url pattern I'm using for this:
path('profile/<str:username>/',views.UserProfile.as_view(),name='user_profile')
So far, the navbar will display the username, seconduser, and when I click the button I'm brought to the url /profile/seconduser/, which is what I want.
The problem is, I'm not able to now use the username in my view to query the objects for the given user. Here is what I have for this view so far
views.py
class UserProfile(TemplateView):
template_name = 'fillups/user_profile.html'
slug_field = "username"
slug_url_kwarg = "username"
def get_context_data(self, **kwargs):
context = super(UserProfile, self).get_context_data(**kwargs)
usr = get_object_or_404(User, username=self.kwargs.get("username"))
overview_stats = {
'total_cars': Car.objects.filter(username=usr).count(),
'total_fillups': Fillup.objects.filter(username=self.request.user).count(),
'total_distance': Fillup.objects.filter(username=self.request.user).aggregate(Sum('trip_distance')),
'total_gallons': Fillup.objects.filter(username=self.request.user).aggregate(total_gallons = Round(Sum('gallons'),4)),
'avg_price': Fillup.objects.filter(username=self.request.user).aggregate(avg_price = Round(Avg('price_per_gallon'),3)),
'total_spent': sum_total_sale(Fillup.objects.filter(username=self.request.user)),
'avg_mpg': avg_mpg(Fillup.objects.filter(username=self.request.user))
}
context['stats'] = overview_stats
context['active_cars'] = Car.objects.filter(status='Active').filter(username=self.request.user)
context['last_10_fillups'] = Fillup.objects.filter(username=self.request.user).order_by('-date')[:10]
return context
For now, everything in the overview_stats dict is what I originally had when I was just querying stuff for the logged-in user, where there was just a simple "myprofile" url. The problem I'm having her is that the get_object_or_404 isn't finding the user. I know that username=self.kwargs.get("username") is getting 'seconduser' like it should be, but for some reason I just can't get the user.
For some extra info, here is one of my models:
class Car(models.Model):
username = models.ForeignKey(User,on_delete=models.CASCADE)
name = models.CharField(max_length=25)
make = models.CharField(max_length=25)
model = models.CharField(max_length=25)
model_year = models.IntegerField(choices=MODEL_YEARS)
status = models.CharField(max_length=10,choices=STATUS,default='Active')
def __str__(self):
return self.name
And in the initial Django tutorial I did, the instructor said it is best to extend the user model so it's easier to make changes, so I have this in a separate app, accounts/models.py
class User(auth.models.User,auth.models.PermissionsMixin):
def __str__(self):
return "#{}".format(self.username)
I've tried using the method in this question which is why I have the slug field stuff in my view currently, and while my question is essentially a duplicate of this question
I've been stuck on this all night and would really appreciate any help, thanks!
Remove the the self from self.kwargs.get("username"). It should be kwargs.get("username").
kwargs is an argument not on object property.
So I have a simple model called Pages. Every Page belongs to a certain category, since this is a ForeignKey relation, a Page can only belong to a single category.
Besides categories we also use tags to furthermore filter the different pages.
We use a category view to display all pages belonging to a certain category, easy peasy.
The thing is, we use django-filters to filter the pages by selecting different tags. The list of tags is increasing by the amount of pages. Therefore I would like to only show related tags to the category.
urls.py
path('<slug:category_slug>/', views.PageByCategoryView.as_view(), name='page_by_category'),
views.py
class PageByCategoryView(FilterView):
logger.info("Category view is called")
model = Page
filterset_class = PageByCategoryFilter
strict = False
queryset = Page.published_objects.all()
template_name = 'pages/page_by_category.html'
filters.py
class PageByCategoryFilter(django_filters.FilterSet):
tags = django_filters.ModelMultipleChoiceFilter(
queryset=Tag.objects.filter(page__category_id='2'), <-- actually works!
conjoined=True,
widget=forms.CheckboxSelectMultiple()
)
class Meta:
model = Page
fields = [
'tags__slug'
]
So the tags used in the filter actually get filtered by page__category_id = 2, this is exactly what I want to achieve though I want to do this dynamically. I tried to define the qs like so;
#property
def qs(self):
queryset = super(PageByCategoryFilter, self).qs
current_category = self.request.GET.get('category_slug')
if current_category:
logger.info("Current category is in url")
return queryset.filter(category__slug=current_category)
return queryset
This just doesn't seem to be working, how can i get the current_category from the url?
Alright, what I did below does actually work but it look kinda hackish..
Doe anyone have a better answer on solving this issue?
def category_filter(request):
path = request.path
category_slug = re.sub('\/+', r'', path)
current_category = category_slug
return Tag.objects.filter(page__category__slug=current_category).distinct()
With best regards,
Kevin
Your original view function should be able to take a parameter category_slug where the category slug of the URL is passed in, like in this example from the Django docs (notice how num is declarad as an int in the URL and then passed as an argument to page):
In urls.py
path('blog/page<int:num>/', views.page),
In your view function
def page(request, num=1):
...
If you're using Django REST, you should be able to get the URL parameter your kwargs member attribute, like so:
In urls.py
url('^purchases/(?P<username>.+)/$', PurchaseList.as_view()),
In your view classes:
class PurchaseList(generics.ListAPIView):
serializer_class = PurchaseSerializer
def get_queryset(self):
"""
This view should return a list of all the purchases for
the user as determined by the username portion of the URL.
"""
username = self.kwargs['username'] # this is what you're after
return Purchase.objects.filter(purchaser__username=username)
I'm trying to achieve this.
1. one page website with a list and detail view on the same template.
when the website loads, I have the thumbnails with all the list of items, I show that using a for loop and listview.
under that i have the same thing(items from the model) but in a form of carousel.( i can see all the items from the model in the slider with complete details)
these work fine.
but I want a detailed view in the same carousel slider. for eg: the user can see the list of items (thumbnail) and when clicked on a item , that particular item should be active in the slider,
1. I cant get the list and detail view to work on the same template.
2.i cant get the detailed view to work in the same carousel slider.
MODELS.PY
class Cheese(models.Model):
TYPES_CHOICES=(
('COW', 'COW'),
('GOAT', 'GOAT'),
('SHEEP', 'SHEEP'),
('BUFFALO', 'BUFFALO'),
('COW, GOAT & SHEEP', 'COW, GOAT & SHEEP'),
('COW & SHEEP', 'COW & SHEEP')
)
COUNTRY_CHOICES=(
('USA', 'USA'),
('UK','UK'),
('ITALY', 'ITALY'),
('FRANCE', 'FRANCE'),
('NETHERLANDS', 'NETHERLANDS')
)
origin = models.CharField(max_length=100)
title = models.CharField(max_length=100)
types = models.CharField(max_length=100, choices=TYPES_CHOICES)
about = models.TextField()
serve = models.CharField(max_length=1000)
image = models.ImageField(
null=True,
blank=True,
width_field="width_field",
height_field= "height_field")
width_field = models.IntegerField(default=550)
height_field = models.IntegerField(default=550)
country = models.CharField(max_length=50, choices=COUNTRY_CHOICES)
timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)
def __unicode__(self):
return self.title
VIEWS.PY
class HomeView(generic.ListView):
template_name= 'cheese/home.html'
model = Cheese
def get(self, request):
queryset_list = Cheese.objects.all()
queryset_wine = Wine.objects.all()
form = FeedbackForm()
#basic search query
query = request.GET.get("q")
if query:
queryset_list = queryset_list.filter(
Q(title__icontains=query)|
Q(origin__icontains=query)|
Q(types__icontains=query)|
Q(about__icontains=query)
).distinct()
context={
'cheese': queryset_list,
'form': form,
'wine': queryset_wine,
}
return render(request, self.template_name, context)
URLS.PY
urlpatterns = [
url(r'^$', views.HomeView.as_view(), name='home'),
url(r'^(?P<pk>[0-9]+)$', views.HomeView.as_view(), name='cheesedetail'),
]
You are using here CBV for the HomeView in a horrible way that I did not almost notice that you ae using CBV. Do not ever override render in get method. I see you are overidding it because of appending some items to context. Then append items to context and do not override the render!
class HomeView(ListView):
template_name = 'cheese/home.html'
query = Cheese.objects.all()
def get_queryset(self):
queryset = super().get_queryset()
search_query = self.request.GET.get("q")
return queryset.filter(
Q(title__icontains=search_query)|
Q(origin__icontains=search_query)|
Q(types__icontains=search_query)|
Q(about__icontains=search_query)
).distinct()
def get_context(self):
context = super().get_context() # gets context from parent method0
context['form'] = FeedbackForm()
context['wines'] = Wine.objects.all()
return context
The main concept of using CBV's is that it tends to be more organised and it's based in inheritance.
You should consider using async javascript API calls (e. g. AJAX) in your templates. You can load/send your content dynamically to the server. Then you should create two separate views - ListView for Cheese with the list of cheese and then another DetailView with search form and getting data based on it. In the template you would define something like window.onload = fetchDetailView() and then the sender methods and you are done.
I have been chopping away at this for a few days with no clue as to why its not working. I have two views, one to list tools and one to list parts. I also have the list and detail URL's for both. Both list views work and the tool detail view works, but when I click on a part item to see the detail, the correct url appears in the browser, but I get an error shown in the screenshot below, which as you can see is trying to use the tool_detail view. Thank you for looking.
Here is my code for review:
url:
from .views import tool_list, part_list, tool_detail, part_detail
urlpatterns = [
url(r'^products/tools/$', tool_list, name='tool_list'),
url(r'^products/(?P<category>[^\.]+)/(?P<slug>[^\.]+)/$', tool_detail, name='tool_detail'),
url(r'^products/parts/$', part_list, name='part_list'),
url(r'^products/(?P<category>[^\.]+)/(?P<slug>[^\.]+)/$', part_detail, name='part_detail'),
]
view:
def tool_list(request):
tools = Tool.objects.prefetch_related('uploads').all()
return render(request, 'tool_list.html', {'tools': tools})
def tool_detail(request, **kwargs):
tool = get_object_or_404(Tool, slug=kwargs.get('slug'))
return render(request, 'tool_detail.html', {'tool': tool})
def part_list(request):
parts = Part.objects.prefetch_related('uploads').all()
return render(request, 'part_list.html', {'parts': parts})
def part_detail(request, **kwargs):
part = get_object_or_404(Part, slug=kwargs.get('slug'))
return render(request, 'part_detail.html', {'part': part})
models
class Part(Timestamp):
model_number = models.ForeignKey(ModelNumber, related_name='part_model_number')
category = models.ForeignKey(Category, related_name='part_category')
price = models.DecimalField(max_digits=10, decimal_places=2)
title = models.CharField(max_length=250)
slug = models.SlugField(help_text="slug-title-should-be-like-this")
...
class Tool(Timestamp):
model_number = models.ForeignKey(ModelNumber, related_name='tool_model_number')
price = models.DecimalField(max_digits=10, decimal_places=2)
title = models.CharField(max_length=250)
slug = models.SlugField(help_text="slug-title-should-be-like-this")
category = models.ForeignKey(Category, related_name='tool_category')
...
The first url pattern that matches is what Django will use to dispatch to a view. The 2nd url matches, so it's using the tool_detail view.
So, I am having some difficulty trying to slugify a title field in my model and still have it return the proper information.
Currently, a user can follow the url, if the list in their account exists under this regular expression:
url(r'^user/(?P<username>\w+)/list/(?P<listname>\w+)/$', mylistpage, name='lists'),
The issue I face is that the user can have a list containing spaces, but the regex bases their url off their list name. I am wanting to implement a slug url, but still have it retrieve the correct model/object information.
I am trying to have a slug field and then pre-populate it based on the list name, but I am lost at how this implementation is supposed to work. Much appreciation in advance from any insight.
Model
class newlist(models.Model):
user = models.ForeignKey(User)
list_name = models.CharField(max_length = 100,)
picture = models.ImageField(upload_to='profiles/', default = "/media/profiles/default.jpg")
slugurl = models.SlugField(default = slugurl(self))
def __str__(self):
return self.list_name
def slugurl(self):
return slugify(self.list_name)
Views
def mylistpage(request, username, listname):
context = RequestContext(request)
#make sure that the user is authenticated
if username == request.user.username:
#If the user is authenticated, then perform the following functions to the page
if request.user.is_authenticated():
#Store the current user request object into a variable
user = User.objects.get(username=username)
#Store the list name to the item that starts with the url input
listname = request.user.newlist_set.filter(list_name__iexact=listname)
listitems = request.user.newlist_set.all()
if not listname:
return redirect('/notfound')
else:
return redirect('/notfound')
return render_to_response('listview.html', {'lista': listname}, context)
I have used django-autoslug to great success. You can find a live example here.
SlugField is just a char field with a little syntactic sugar.
You will want to name your slug just slug so django can find it automatically in the URL resolution and passes the right parameter to views.
Your amended code would look like:
from autoslug import AutoSlugField
from django.db import models
class Newlist(models.Model): # Classes start with uppercase names by default
user = models.ForeignKey(User)
list_name = models.CharField(max_length = 100,)
picture = models.ImageField(upload_to='profiles/', default = "/media/profiles/default.jpg")
slug = AutoSlugField(populate_from='list_name')
def __str__(self):
return self.list_name
Your View:
def mylistpage(request,username, slug):
context = RequestContext(request)
#make sure that the user is authenticated
if username == request.user.username:
#If the user is authenticated, then perform the following functions to the page
if request.user.is_authenticated():
#Store the current user request object into a variable
user = User.objects.get(username=username)
#Store the list name to the item that starts with the url input
listname = request.user.newlist_set.filter(slug=slug)
listitems = request.user.newlist_set.all()
if not listname:
return redirect('/notfound')
else:
return redirect('/notfound')
return render_to_response('listview.html', {'lista': listname}, context)
urls.py
url(r'^user/(?P<username>\w+)/list/(?P<slug>[\w-]+)/$', mylistpage, name='lists'),