Django - Combining a datatable view and form view - django

I am trying to put a table from datatables and a crispy form into one view so that I can display it on a single page in django. I am completely stuck on how to combine them as i'm still a newbie.
Any help is greatly appreciated!!
Views.py ------------- Datatable below
class CustomerTable(XEditableDatatableView):
template_name = "customers.html"
model = Customer
datatable_options = {
'columns': [
("Title", 'Title'),
("Name", 'PoC', helpers.make_xeditable),
("Email", 'PoCEmail', helpers.make_xeditable),
("Location", 'Location', helpers.make_xeditable),
("DateAdded", 'DateAdded', helpers.make_xeditable),
],
'hidden_columns': ['ID'],
}
-----------Crispy Form below
def CustomerView(request):
form = CustomersForm(request.POST or None)
success = False
if request.method == 'POST':
if form.is_valid():
form.save()
form = CustomersForm()
success = True
if request.POST.get('delete'):
obj.delete()
customer_form = CustomersForm()
return render(request, 'customers.html', {'customer_form': customer_form})
urls.py ----------------
urlpatterns = patterns('',
url(r'^$', views.CustomerTable.as_view(), name='customertable'),
url(r'^$', 'ISL.views.CustomerView', name='customersform'),
)
Thanks for any help!
W

You can try to use get_context_data() method of your class like this:
def get_context_data(self, **kwargs):
context = super(CustomerTable, self).get_context_data(**kwargs)
# do your magic here <<<
# then
context['customer_form'] = customer_form
return context
First you are calling ancestor's get_context_data method via super built-in function, then you can append to context everything you want, in your example it is custom form. After this, you can access 'custom_form' in your template.
Unfortunately, i cant test it at the moment, but this method must be appended in ancestor by ContextMixin (django doc about this method: https://docs.djangoproject.com/en/1.8/ref/class-based-views/mixins-simple/).

Related

How to pass data to a class based view's method?

I am trying to figure out how I can pass data from a form to the method of a class based view that serves as a API endpoint.
Homepage view (has a form to enter a stock ticker):
def home(request):
# data = get_stock_data('TSLA', key)
if request.method == 'POST':
form = TickerForm(request.POST)
if form.is_valid():
ticker = form.cleaned_data['ticker']
stock_data = get_stock_data(ticker, api_key)
return redirect('chart-data', ticker=ticker) # this line I am having trouble with
else:
form = TickerForm()
stock_data = None
return render(request, 'app/home.html', {'data': stock_data, 'form':form})
The API View:
class ChartData(APIView):
authentication_classes = []
permission_classes = []
def get(self, request, ticker, format=None):
# get stock data
stock_data = get_stock_data(ticker, api_key) # how do I pass the ticker?
labels = []
default_items = []
# get label & values
for key, value in stock_data.items():
labels.append(key)
default_items.append(value['4. close'])
# prepare data
data = {
'labels': labels[::-1],
'default_items': default_items[::-1]
}
return Response(data)
urls.py
urlpatterns = [
path('', views.home, name="homepage"),
path('api/chart/data', views.ChartData.as_view(), name="chart-data"),
]
Then I get the data with Javascript and display the graph on the frontend, which works fine. The only thing I can't figure out how to pass the ticker argument to the get method of my ChartData view. I hope my problem is clear.
return redirect('chart-data', {'ticker':ticker})
Have to use a dictionary, I believe ...
You should include ticker in the url path.
path('api/chart/data/<slug:ticker>/', views.ChartData.as_view(), name="chart-data"),
This assumes ticker is a string like 'TSLA'.

Django, getting data in different page from view

Hello I am new to Django, I am currently working on a project but I can`t figure out how I should do something.
Right now I am at the page
home/stats/ID/LANGUAGE
but inside this page I want a "edit" button, once clicked I want to go to:
home/stats/ID/LANGUAGE/edit/
I just want to get the same data from home/stats/ID/LANGUAGE again, but now in home/stats/ID/LANGUAGE/edit/
My view.py:
class StatsView(TemplateView):
template_name = 'home/stats.html'
analytics = build('analyticsreporting', 'v4', credentials=credentials)
def get(self, request, id, language):
min_date = "2018-12-01"
date01 = datetime.strptime(min_date, '%Y-%m-%d')
max_date = "2018-12-31"
date02 = datetime.strptime(max_date, '%Y-%m-%d')
print(date01)
print(date02)
if request.GET.get('date1'):
date1 = request.GET.get('date1')
pol1 = datetime.strptime(date1, '%Y-%m-%d')
date01 = pol1
print(date01)
if request.GET.get('date2'):
date2 = request.GET.get('date2')
pol2 = datetime.strptime(date2, '%Y-%m-%d')
date02 = pol2
print(date02)
if request.user.is_authenticated:
current_user = request.user.id
result = BlablaAuth.objects.filter(user=request.user)
if language == 'ALL':
blabla = Blabla.objects.filter(blabla=id)
prefix = '/blabla/' + id
if result and blabla.count() > 0:
analytics_result1 = self.analytics.reports().batchGet(
body={
"Google analytics reporting stuff"
analytics_result2 = self.analytics.reports().batchGet(
body={
"Google Reporting stuff"
return render(request, self.template_name, context={
"report1": analytics_result1.execute(),
"report2": analytics_result2.execute()
})
else:
apple = Apple.objects.filter(video=id)
prefix = '/apple/' + id
if result and apple.count() > 0:
analytics_result1 = self.analytics.reports().batchGet(
body={
"Google reporting analytics stuff"
analytics_result2 = self.analytics.reports().batchGet(
body={
"Google reporting analytics stuff"
return render(request, self.template_name, context={
"report1": analytics_result1.execute(),
"report2": analytics_result2.execute()
})
My urls.py:
from django.conf.urls import url
from django.contrib import admin
from home.views.views import HomeView, StatsView
from .views import views
from django.contrib.auth.decorators import login_required
app_name = "home"
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^stats/(?P<id>[0-9]+)/(?P<language>[a-zA-Z]+)/$',
login_required(StatsView.as_view()), name='stats'),
url(r'^stats/(?P<id>[0-9]+)/(?P<language>[a-zA-Z]+)/edit/$',
StatsView.edit_stats, name='stats_edit'),
url(r'^$', login_required(HomeView.as_view()), name='home'),
]
My button in stats.html:
<button><a href="{% url home:stats_edit auth.blabla.id apple.language %}">Edit</button>
Assuming your edit view will be based on a generic CBV (e.g. UpdateView), you can create a Mixin class that has a method get_context_data(self, **kwargs) and does all the stuff you now do in the get() method of your TemplateView. This method will automatically get called by your TemplateView and UpdateView and add the context to your rendering.
class AnalyticsMixin(object):
analytics = None # or some default that can be used by all subclasses.
# None is not a good default since it will raise an AttributeError when calling self.analytics.reports()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# all the stuff you do in get() method, using self.request and self.kwargs which are set by the `as_view()` method on the CBV
request = self.request
id = self.kwargs.get('id')
language = self.kwargs.get('language')
...
return context.update({
"report1": analytics_result1.execute(),
"report2": analytics_result2.execute()
})
then in your views:
class StatsView(AnalyticsMixin, TemplateView):
template_name = ...
analytics = ... # override here or if it depends on the request override in a method
# remove the get() method unless you do something that's not for both views.
# e.g. to override self.analytics if credentials depends on request
def get_context_data(self, **kwargs):
self.analytics = build('analyticsreporting', 'v4', credentials=self.get_credentials())
return super().get_context_data(**kwargs)
and for your edit view:
class EditStatsView(AnalyticsMixin, UpdateView):
template_name = ...
model = ... # name of model to update
# here add the form_class for the editing form if you customise it
# the context will have the form + the data from get_context_data()
way to pass data between two view is session . django support authenticate as well as anonymous session. just store the data as session key and retrieve it when ever you need it.

How can I add an Upload File field to a Wagtail Form?

I want to make File Upload a possible Wagtail Form field type on form pages. How do I configure the models to make this possible? Please note I am more interested in allowing users to upload document files like PDFs rather than images.
Here is a great article from LB Johnson on that matter. The code below is directly taken from the article (but copying here in case the article goes away), however you might want to follow the article as it explains everything step by step.
It's quite involved at the moment, you'll need to:
extend AbstractFormField to define a new field type.
extend FormBuilder to deal with the new field type.
set form_builder to your custom FormBuilder class on your FormPage .
override the serve method on the FormPage to pass the file data to the form (only if you use Wagtail 1.12 and below as it does it automatically from Wagtail 1.13)
override the process_form_submission to process the file
Here is the full code:
from wagtail.wagtailforms.models import AbstractFormField, FORM_FIELD_CHOICES
from wagtail.wagtailforms.forms import FormBuilder
from wagtail.wagtailimages.fields import WagtailImageField
def filename_to_title(filename):
from os.path import splitext
if filename:
result = splitext(filename)[0]
result = result.replace('-', ' ').replace('_', ' ')
return result.title()
class FormField(AbstractFormField):
FORM_FIELD_CHOICES = list(FORM_FIELD_CHOICES) + [('image', 'Upload Image')]
field_type = models.CharField(
verbose_name=_('field type'),
max_length=16,
choices=FORM_FIELD_CHOICES)
page = ParentalKey('FormPage', related_name='form_fields')
class ExtendedFormBuilder(FormBuilder):
def create_image_upload_field(self, field, options):
return WagtailImageField(**options)
FIELD_TYPES = FormBuilder.FIELD_TYPES
FIELD_TYPES.update({
'image': create_image_upload_field,
})
class FormPage(AbstractEmailForm):
form_builder = ExtendedFormBuilder
def serve(self, request, *args, **kwargs):
if request.method == 'POST':
# form = self.get_form(request.POST, page=self, user=request.user) # Original line
form = self.get_form(request.POST, request.FILES, page=self, user=request.user)
if form.is_valid():
self.process_form_submission(form)
return render(
request,
self.get_landing_page_template(request),
self.get_context(request)
)
else:
form = self.get_form(page=self, user=request.user)
context = self.get_context(request)
context['form'] = form
return render(
request,
self.get_template(request),
context
)
def process_form_submission(self, form):
cleaned_data = form.cleaned_data
for name, field in form.fields.iteritems():
if isinstance(field, WagtailImageField):
image_file_data = cleaned_data[name]
if image_file_data:
ImageModel = get_image_model()
image = ImageModel(
file=cleaned_data[name],
title=filename_to_title(cleaned_data[name].name),
collection=self.upload_image_to_collection,
# assumes there is always a user - will fail otherwise
uploaded_by_user=form.user,
)
image.save()
cleaned_data.update({name: image.id})
else:
# remove the value from the data
del cleaned_data[name]
form_data = json.dumps(cleaned_data, cls=DjangoJSONEncoder)
submission_object = dict(
page=self,
form_data=form_data,
user=form.user,
)
intro = RichTextField(blank=True)
thank_you_text = RichTextField(blank=True)
FormPage.content_panels = [
FieldPanel('title', classname="full title"),
FieldPanel('intro', classname="full"),
InlinePanel('form_fields', label="Form fields"),
FieldPanel('thank_you_text', classname="full"),
MultiFieldPanel([
FieldRowPanel([
FieldPanel('from_address', classname="col6"),
FieldPanel('to_address', classname="col6"),
]),
FieldPanel('subject'),
], "Email"),
]
Here is the (untested) code to handle a document instead of an image:
from django.forms import FileField
from wagtail.wagtaildocs.models import get_document_model
# Other imports
class FormField(AbstractFormField):
FORM_FIELD_CHOICES = list(FORM_FIELD_CHOICES) + [('document', 'Upload Document')]
# `field_type` and `page` remain unchanged
class ExtendedFormBuilder(FormBuilder):
def create_document_upload_field(self, field, options):
return FileField(**options)
FIELD_TYPES = FormBuilder.FIELD_TYPES
FIELD_TYPES.update({
'document': create_document_upload_field,
})
class FormPage(AbstractEmailForm):
# `form_builder` attribute and `serve` remain unchanged.
def process_form_submission(self, form):
cleaned_data = form.cleaned_data
for name, field in form.fields.iteritems():
if isinstance(field, FileField):
document_file_data = cleaned_data[name]
if document_file_data:
DocumentModel = get_document_model()
document = DocumentModel(
file=cleaned_data[name],
title=filename_to_title(cleaned_data[name].name),
# assumes there is always a user - will fail otherwise
uploaded_by_user=form.user,
)
document.save()
cleaned_data.update({name: document.id})
else:
# remove the value from the data
del cleaned_data[name]
# The rest of the function is unchanged
Now there are some changes made by wagtail developer in FormBuilder So in Below given code is not working now
class ExtendedFormBuilder(FormBuilder):
def create_document_upload_field(self, field, options):
return FileField(**options)
FIELD_TYPES = FormBuilder.FIELD_TYPES
FIELD_TYPES.update({
'document': create_document_upload_field,
})
Instead of this they made
class ExtendedFormBuilder(FormBuilder):
def create_document_upload_field(self, field, options):
return FileField(**options)
FIELD_TYPES = FormBuilder.formfields
Wagtail Core developer changed FormBuilder.FIELD_TYPES property to FormBuilder.formfields
So i hope this is helpful for developer to let you know which changes happened recently.

Pre-populating Model Form with object data - Django

I have tried various options for this but no luck so far. I am trying to get instance data to be pre-populated into my ModelField. Here is what I have:
forms.py
class edit_project_info(ModelForm):
project_name = forms.CharField(max_length=150)
class Meta:
model = Project
exclude = ['project_type', 'created_date', 'start_date', 'end_date', 'pm_scope', 'dev_scope', 'design_scope', 'testing_scope' ]
View.py
def edit_project (request, offset):
this_project = Project.objects.get(pk=offset)
data = {'project_name' : 'abc'}
if request.method == 'POST':
form = edit_project_info(request.POST, instance=this_project, initial=data)
if form.is_valid():
form.save()
return HttpResponseRedirect('/project_profile/%s/' % offset)
else:
form = edit_project_info()
All I get is an empty field. I can add the initial value to forms.py, but then it is static rather than populated based on the form instance. What I have done here with creating a dict and then passing it to initial in the form instance does not seem to do anything. I'm sure I am missing something basic. Any help would be great! Thanks ahead of time.
Two last lines recreate your form variable. Just remove else: form = edit_project_info():
def edit_project (request, offset):
this_project = Project.objects.get(pk=offset)
data = {'project_name' : 'abc'}
form = edit_project_info(request.POST, instance=this_project, initial=data)
if request.method == 'POST':
if form.is_valid():
form.save()
return HttpResponseRedirect('/project_profile/%s/' % offset)
# else:
# form = edit_project_info()
# ...

django migrating to 1.6 of an old app: ListView

I am migrating our old app to django 1.6.
Now, some views have been programmed this way:
from django.views.generic.list_detail import object_list
#render_to("items/index.html")
def index(request):
profile = request.user.get_profile()
args = clean_url_encode(request.GET.copy().urlencode())
context = {
'is_dashboard': True,
'body_id': 'dashboard',
'object_list': None,
'args':args,
'show_in_process':False
}
return context
I know that I need to use the new ListView now, but the examples and docs don't seem to tell me this particular case I have: the object_list being passed in the context.
How can I adapt this code to use the new class based generic view? Can I also just use ListView.asView() instead of 'object_list':None ?
Why are you using ListView if you don't have a object list? I think TemplateView should do the job. You just need to override get_context_data and provide custom context
class IndexView(TemplateView):
template_name = 'items/index.html'
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
profile = self.request.user.get_profile()
args = clean_url_encode(self.request.GET.copy().urlencode())
context.update({
'is_dashboard': True,
'body_id': 'dashboard',
'object_list': None,
'args':args,
'show_in_process':False
})
return context