Get view pk from django-cms CMSAttachMenu - python

I have a custom app inside django-cms and have the need to attach a submenu to my app.
I've followed the guides and examples I've found to do this (see the Portfolio example given by Brandon here: custom views within Djangocms?), and managed to get the submenu up and running.
By expanding on the example provided above; What if this Portfolio app presented here, consisted of a small number of different views (create view, detail view and perhaps a couple of other related views). What if I needed to build a submenu to hold the choices related to user navigation in this small app. And what if the navigation should present choices based on selected content in the views ("Edit" only if a portfolio is selected or similar).
The submenu would have to know what Portfolio was selected, right? Or at least that a Portfolio is in fact selected and in view.
How can I transfer to my implementation of CMSAttachMenu what my view allready knows? In my case, I'm implementing an app dealing with meetups or "Events". The example below does not work, because the Event object is obviously not registered in the request, but it illustrates what I want:
# menu.py
from django.core.urlresolvers import reverse
from menus.base import NavigationNode
from menus.menu_pool import menu_pool
from cms.menu_bases import CMSAttachMenu
from App.apps.event.models import Event
from django.utils.translation import ugettext_lazy as _
import logging
logger = logging.getLogger('instant.event')
class EventMenu(CMSAttachMenu):
name = _("Event Sub-Menu")
def get_nodes(self, request):
nodes = []
nodes.append(NavigationNode(_('Create new events'), reverse("admin:event_event_add"), 1 + len(nodes), 0))
if hasattr(request, 'event'):
if request.event.is_registered_to_event(request.user):
nodes.append(NavigationNode(_('Unregister from this event'), reverse("unregister_from_event"), 1 + len(nodes), 0))
else:
nodes.append(NavigationNode(_('Register to participate in this event'), reverse("unregister_from_event"), 1 + len(nodes), 0))
if request.user.is_superuser():
nodes.append(NavigationNode(_('Register other participant to this event'), reverse("register_admin", args=(request.event.id)), 1 + len(nodes), 0))
nodes.append(NavigationNode(_('Back to list of events'), reverse("events"), 1 + len(nodes), 0))
return nodes
menu_pool.register_menu(EventMenu)

This was a hard one, but the following would solve it (showing only relevant parts):
cms_app.py
from cms.app_base import CMSApp
from cms.apphook_pool import apphook_pool
from django.utils.translation import ugettext_lazy as _
class EventsApphook(CMSApp):
name = _("Event")
urls = ["App.apps.event.urls"]
apphook_pool.register(EventsApphook)
menu.py
from cms.menu_bases import CMSAttachMenu
from menus.base import NavigationNode
from menus.menu_pool import menu_pool
from django.utils.translation import ugettext_lazy as _
menuNodes = []
class EventMenu(CMSAttachMenu):
name = _("Event Sub-Menu")
def get_nodes(self, request):
return menuNodes
menu_pool.register_menu(EventMenu)
def add_menu_node(text, url):
# only add a given url once
if len(list(n for n in menuNodes if n.url == url)) == 0:
menuNodes.append(NavigationNode(text, url, 1 + len(menuNodes), 0))
menu_pool.clear()
views.py
from django.views.generic.detail import DetailView
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from App.apps.event.menu import add_menu_node
from App.apps.event.models import Event
class EventMenuMixin(object):
def get_context_data(self, **kwargs):
context = super(EventMenuMixin, self).get_context_data(**kwargs)
member = self.request.user
if 'pk' in self.kwargs.keys():
event = Event.objects.get(id=self.kwargs['pk'])
if event.is_registered_to_event(member):
add_menu_node(_('Unregister from this event'), reverse("unregister_from_event"))
else:
add_menu_node(_('Register to participate in this event'), reverse("register_to_event", args=(self.kwargs['pk'])))
add_menu_node(_("Create new events"), reverse("admin:event_event_add"))
return context
class EventDetailView(EventMenuMixin, DetailView):
model = Event
template_name = 'event/event_detail.html'
context_object_name = 'event'
I hope this will help others in the same predicament as me.

Related

return a generated HTML file in django rest framework

I have the following view, which takes in the data passed in by the user and returns an HTML file (folium map).
I have three questions:
firstly, this view raises an error, in the browser.
TypeError at /locations/
'LocationInfo' object is not iterable
and second,
How do I return the generated HTML file to the user?
third,
I want that when the user inputs the data, the logic will run and return said HTML file.
My business logic here uses the values inputted by the user to plot the map and generates an HTML file saved in the directory, I can return the path to said file, or I have made another option that opens the HTML in another window automatically.
# views.py
from rest_framework.viewsets import ModelViewSet
from .serializers import LocationInfoSerializer
from .models import LocationInfo
from three_Positions_plotting.app import main
def map_info_to_logic(gdt1, gdt2, uav):
html_file = main(gdt1=gdt1, gdt2=gdt2, uav=uav)
return html_file
class LocationInfoViewSet(ModelViewSet):
queryset = LocationInfo.objects.latest('date_added')
serializer_class = LocationInfoSerializer
serializer = LocationInfoSerializer(queryset, many=False)
values = list(serializer.data.values())
gdt1 = [values[1], values[2]]
gdt2 = [values[2], values[3]]
uav = [values[4], values[5]]
data = map_info_to_logic(
gdt1=gdt1,
gdt2=gdt2,
uav=uav
)
My logic running point:
import numpy as np
from Project_Level.angle_condition import MeetAngleCond
from Project_Level.plot_folium import PlotOnMap
from Project_Level.utils import convert_to_xy
from triangulationapi.three_Positions_plotting.dataframes import GetDataToGeoPandas
from triangulationapi.three_Positions_plotting.positions_data_collecting import PositionsData
def main(gdt1: list, gdt2: list, uav: list):
# Get the complete latitude, longitude, lists.
positions_data = PositionsData(gdt1=gdt1,
gdt2=gdt2,
uav=uav)
full_lat_lon_list, lat_list, lon_list = positions_data.calculate_list_lens()
# Get cartesian coordinates for every point.
gdt1_xy = np.asarray(convert_to_xy(gdt1))
gdt2_xy = np.asarray(convert_to_xy(gdt2))
# Get the angle for every point in f_lat_lon_list
angles_list = MeetAngleCond()
plot_angles_list = angles_list.create_angles_list(lat_lon_list=full_lat_lon_list,
arrayed_gdt1=gdt1_xy,
arrayed_gdt2=gdt2_xy,
uav_elev=uav[-1])
get_final_lists = GetDataToGeoPandas()
lat_lon_a, lat_lon_b, optimal = get_final_lists.create_gpd_and_final_lists(angles_list=plot_angles_list,
lat_list=lat_list,
lon_list=lon_list)
# Initialize a folium map.
plot = PlotOnMap(lat_lon_a=lat_lon_a,
lat_lon_b=lat_lon_b,
optimal=optimal)
plot.create_map(mid_point=gdt1, zoom=13)
# Plot relevant locations.
plot.plot_gdt_and_triangulation(gdt1=gdt1, gdt2=gdt2, uav=uav)
# add some plugins to the map.
plot.plugins()
# return the generated html file with the map.
html_file = plot.return_html_link()
# auto opens the file.
#auto_open = plot.auto_open(html_map_file='index.html',
# path='/home/yovel/PycharmProjects/Triangulation-#Calculator/triangulationapi/three_Positions_plotting/index'
# '.html '
# )
You don't have a set to show, you should use the RetrieveModelMixin with the GenericAPIView. e.g: replace the line class LocationInfoViewSet(ModelViewSet):
, with this line:
class LocationInfoViewSet(GenericAPIView, RetrieveModelMixin):
the returned html should be on the LocationInfoSerializer, as one of it's field.

Django ORM select_related AttributeError

In my project im trying to use an external .py file for manage data using my django app style like this:
In my django project i create a model:
class temp_test_keywords(models.Model):
main_id = models.ForeignKey(temp_main)
test_id = models.ForeignKey(temp_case)
key_id = models.ForeignKey(temp_keywords)
variable_id = models.ForeignKey(temp_variables, null=True, blank=True)
def __str__(self):
return '%s -> %s' % (str(self.main_id), str(self.test_id))
Well, now in my external rst.py file i start django env like this:
import sys
import os
import django
sys.path.append('core')
os.environ['DJANGO_SETTINGS_MODULE'] = 'core.settings'
django.setup()
ok, at this point i import table and create class for do some thinks with it:
from django.db import models
from django.contrib.contenttypes.fields import
GenericForeignKey,GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.db.models import Count
from frontend.models import temp_test_keywords
class PrepareRst:
def __init__(self,test_id,t_type,log=False):
self.rst = self.mainprep(test_id,t_type)
def mainprep(self,test_id,t_type):
return self.tc_prep(test_id)
#TestCase rst prep method
def tc_prep(self,test_id):
maxpar = temp_test_keywords.objects.filter(main_id = test_id).values('key_id').annotate(total=Count('variable_id')).order_by('-total').first()
totpar = maxpar['total']
#Part1 list creation
count = 0
ltouple = ()
l1 = ["Test Case"]
while (count < totpar):
l1.append("")
count += 1
ltouple += (l1,)
#Query for extract keywords, values
kv = temp_test_keywords.select_related()
but when i run an AttributeError: type object 'temp_test_keywords' has no attribute 'select_related' error raise
if i start python manage.py shell from terminal the "kv = temp_test_keywords.select_related()" command works fine, why in my .py code doesn't?
Thanks in advance
Try,
kv = temp_test_keywords.objects.all().select_related()

Django Update object only when foreign key object will change

I want to update my object's min and max price when Item's foreign key object(Currency) will be updated. In this situation it updates after every refresh of page.
my views.py
for item in object_list:
if item.currency.id == 2:
new_min_price = item.min_price * (dollar_rate.value)
new_max_price = item.max_price * (dollar_rate.value)
item.min_price = new_min_price
item.max_price = new_max_price
item.save()
You can use signals to solve this: https://docs.djangoproject.com/en/1.10/topics/signals/
from django.db.models.signals import post_save
from django.dispatch import receiver
from . models import Currency
# This gets called immediately after any Currency object is saved
#receiver(post_save, sender=Currency)
def update_min_max(sender, **kwargs):
currency = kwargs.get('instance')
item = currency.item_set.get(id=2)
new_min_price = item.min_price * (dollar_rate.value)
new_max_price = item.max_price * (dollar_rate.value)
item.min_price = new_min_price
item.max_price = new_max_price
item.save()
You might need to make some minor adjustments but this is the general idea. pre_save could be better in your case rather than post_save.

django-cms default menu extend Menu or CMSAttachMenu?

I am trying to build a very simple, wiki-type site using django-cms.
I have 1 app, with 2 models defined:
class Subject(models.Model):
label=models.CharField
class Topic(models.Model):
...
cat = models.ForeignKey('topics.Category',
blank=True,
default=None,
help_text=u'Please choose a category for this topic',
null=True
)
I am trying to have the default menu show the Subject classes as the top-level options, with the Topic classes as sub-levels for each Subject. There will be 4 Subjects altogether. E.g.:
Subject 1
-topic1
-topic2
Subject 2
-topic3
-topic4
etc..
I have read all the django-cms docs, and I'm still confused. In my menu.py, should I be extending Menu or CMSAttachMenu? Do I need 4 different generators? How do I reference the ForeignKey field when using the generator?
I am a beginner, any help is greatly appreciated
You could do something like that:
# menu.py
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from cms.menu_bases import CMSAttachMenu
from menus.base import NavigationNode
from menus.menu_pool import menu_pool
from .models import Subject
class SubjectsMenu(CMSAttachMenu):
name = _("Subjects Menu")
def get_nodes(self, request):
nodes = []
cnt = 0
for subject in Subjects.objects.all():
subject_node_id = cnt
node = NavigationNode(
subject.label,
reverse('subject_view_detail', args=(subject.pk,)),
subject_node_id
)
nodes.append(node)
for topic in subject.topics.all():
cnt += 1
node = NavigationNode(
topic.name,
reverse('topic_view_detail', args=(topic.pk,)),
cnt,
subject_node_id # parent
)
nodes.append(node)
cnt += 1
return nodes
menu_pool.register_menu(SubjectsMenu)
Then you can add this menu to your AppHook or attach it from the admin.

Autocomplete-Light channel name conflict

I have a Django project containing two apps, Expenses and Sales which both have models named Item. I'm using django-autocomplete-light to ease the selection of Item. This works for either Expenses or Sales depending on which channel I register last but the other one wrongly shows the same Items.
autocomplete_light_registry.py
from sales.models import Item as SalesItem
from expenses.models import Item as ExpenseItem
class ExpenseChannel(autocomplete_light.ChannelBase):
def query_filter(self, results):
q = self.request.GET.get('q', None)
if q:
if results.model == ExpenseItem:
results = results.filter(
Q(name__icontains=q)
return results
class SalesChannel(autocomplete_light.ChannelBase):
def query_filter(self, results):
q = self.request.GET.get('q', None)
if q:
if results.model == SalesItem:
results = results.filter(
Q(name__icontains=q)
return results
autocomplete_light.register(ExpenseItem, ExpenseChannel, placeholder='Select an item (e)')
autocomplete_light.register(SalesItem, SalesChannel, placeholder='Select an item (s)')
admin.py
For sales app, similar in expenses
import autocomplete_light
class SalesItemInline(admin.TabularInline):
fields = ('item', )
model = SalesItem
form = autocomplete_light.modelform_factory(SalesItem)
Checking the log when using the autocomplete fields i see the same url being fetched from both views.
"GET /autocomplete/channel/ItemChannel/?q= HTTP/1.1" 200 1416
How do I configure this so list of sales.Item is returned in Admin Sales view and list of expenses.Item is returned in Admin Expenses view?
What's happening is that the channel class is generated in most cases and it's name is generated too. However, you can avoid channel class generation and channel name generation (hopefully, or this would really suck).
From the registry documentation:
Three cases are possible:
specify model class and ModelNameChannel will be generated extending ChannelBase, with attribute model=model
specify a model and a channel class that does not have a model attribute, and a ModelNameChannel will be generated, with attribute
model=model
specify a channel class with a model attribute, and the channel is directly registered
The solution to avoid channel class generation is to be in the third case: register a model and channel class with a model attribute.
autocomplete_light_registry.py
from sales.models import Item as SalesItem
from expenses.models import Item as ExpenseItem
class ExpenseChannel(autocomplete_light.ChannelBase):
placeholder='Select an item (e)'
model = ExpenseItem
def query_filter(self, results):
q = self.request.GET.get('q', None)
if q:
if results.model == ExpenseItem:
results = results.filter(
Q(name__icontains=q)
return results
class SalesChannel(autocomplete_light.ChannelBase):
model = SalesItem
placeholder = 'Select an item (s)'
def query_filter(self, results):
q = self.request.GET.get('q', None)
if q:
if results.model == SalesItem:
results = results.filter(
Q(name__icontains=q)
return results
autocomplete_light.register(ExpenseChannel)
autocomplete_light.register(SalesChannel)
That would work up to 0.7rc2.
Starting 0.7rc3 (to be released when the pending issue is closed), register() has a new keyword argument, channel_name, which you may use.
But you should be careful with your code, it seems like the query_filter() implementation from your classes is the same as the default implementation ...

Categories