I already add cash_on_delivery payment method to my project. But i want to add one more method. How i can do it. At this moment i have code of checkout views like this:
class PaymentDetailsView(PaymentDetailsView):
template_name = 'checkout/payment-details.html'
template_name_preview = 'checkout/preview.html'
def get_context_data(self, **kwargs):
ctx = super(PaymentDetailsView, self).get_context_data(**kwargs)
ctx['signature'] = gateway.hmac_gen(ctx)
ctx['amount'] = '%s' % ctx['order_total'].incl_tax
ctx['price'] = '%s' % ctx['basket'].lines.all()[0].price_incl_tax
ctx['source'] = ctx
return ctx
# def handle_payment_details_submission(self, request):
# # Validate the submitted forms
# shipping_address = self.get_shipping_address(
# self.request.basket)
# address_form = BillingAddressForm(shipping_address, request.POST)
#
# if address_form.is_valid():
# address_fields = dict(
# (k, v) for (k, v) in address_form.instance.__dict__.items()
# if not k.startswith('_') and not k.startswith('same_as_shipping'))
# self.checkout_session.bill_to_new_address(address_fields)
# return self.render_preview(request, billing_address_form=address_form)
#
# # Forms are invalid - show them to the customer along with the
# # validation errors.
# return self.render_payment_details(
# request, billing_address_form=address_form)
def handle_payment(self, order_number, total, **kwargs):
reference = gateway.create_transaction(order_number, total)
source_type, is_created = SourceType.objects.get_or_create(
name='Cash on Delivery')
source = Source(
source_type=source_type,
currency=total.currency,
amount_allocated=total.incl_tax,
amount_debited=total.incl_tax
)
self.add_payment_source(source)
self.add_payment_event('Issued', total.incl_tax, reference=reference)
Maybe i can make with payment like with adding shipping methods?
Docdata was a very good inspiration for my implementation.
Multiple payment methods, means you will first have to list all methods somewhere (for easy enable/disable or something else) and then decide what methods are applicable to given user (depending on currency, basket size, location, etc.) All this can be handled by extending the PaymentMethodView.
settings.py
from oscar import get_core_apps as get_oscar_apps
...
INSTALLED_APPS = [
...
] + get_oscar_apps([
'checkout',
]) + [
'cashondelivery', # https://github.com/ashishnitinpatil/django-oscar-cash-on-delivery
'custom_payment', # my local app for custom payment gateway
]
OSCAR_PAYMENT_METHODS = (
('cod', _('Cash on delivery')),
('custom_payment', _('Credit / Debit card')),
)
...
checkout/forms.py
from django import forms
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
class PaymentMethodForm(forms.Form):
"""
Extra form for the custom payment method.
"""
payment_method = forms.ChoiceField(
label=_("Select a payment method"),
choices=settings.OSCAR_PAYMENT_METHODS,
widget=forms.RadioSelect()
)
def get_payment_method_display(payment_method):
return dict(settings.OSCAR_PAYMENT_METHODS).get(payment_method)
checkout/views.py
from django.utils import six
from django.conf import settings
from django.shortcuts import redirect
from django.views.generic import FormView
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse, reverse_lazy
from . import forms
from custom_payment import facade
from cashondelivery import gateway as cod_gateway
from oscar.apps.checkout import exceptions
from oscar.core.loading import get_model, get_class
Source = get_model("payment", "Source")
SourceType = get_model("payment", "SourceType")
RedirectRequired = get_class("payment.exceptions", "RedirectRequired")
UnableToPlaceOrder = get_class('order.exceptions', 'UnableToPlaceOrder')
OscarPaymentMethodView = get_class("checkout.views", "PaymentMethodView")
OscarPaymentDetailsView = get_class("checkout.views", "PaymentDetailsView")
OscarShippingMethodView = get_class("checkout.views", "ShippingMethodView")
# Sample pre-condition
class CheckCountryPreCondition(object):
"""DRY class for check country in session pre_condition"""
def get_pre_conditions(self, request):
if 'check_country_in_session' not in self.pre_conditions:
return self.pre_conditions + ['check_country_in_session']
return super().get_pre_conditions(request)
def check_country_in_session(self, request):
if request.session.get('country', None) is None:
raise exceptions.FailedPreCondition(
url=reverse('checkout:shipping-address'),
)
# Inspired by https://github.com/django-oscar/django-oscar-docdata/blob/master/sandbox/apps/checkout/views.py
class PaymentMethodView(CheckCountryPreCondition, OscarPaymentMethodView, FormView):
"""
View for a user to choose which payment method(s) they want to use.
This would include setting allocations if payment is to be split
between multiple sources. It's not the place for entering sensitive details
like bankcard numbers though - that belongs on the payment details view.
"""
template_name = "checkout/payment_method.html"
step = 'payment-method'
form_class = forms.PaymentMethodForm
success_url = reverse_lazy('checkout:payment-details')
pre_conditions = [
'check_basket_is_not_empty',
'check_basket_is_valid',
'check_user_email_is_captured',
'check_shipping_data_is_captured',
'check_payment_data_is_captured',
]
skip_conditions = ['skip_unless_payment_is_required']
def get(self, request, *args, **kwargs):
# if only single payment method, store that
# and then follow default (redirect to preview)
# else show payment method choice form
if len(settings.OSCAR_PAYMENT_METHODS) == 1:
self.checkout_session.pay_by(settings.OSCAR_PAYMENT_METHODS[0][0])
return redirect(self.get_success_url())
else:
return FormView.get(self, request, *args, **kwargs)
def get_success_url(self, *args, **kwargs):
# Redirect to the correct payments page as per the method (different methods may have different views &/or additional views)
return reverse_lazy('checkout:preview')
def get_initial(self):
return {
'payment_method': self.checkout_session.payment_method(),
}
def form_valid(self, form):
# Store payment method in the CheckoutSessionMixin.checkout_session (a CheckoutSessionData object)
self.checkout_session.pay_by(form.cleaned_data['payment_method'])
return super().form_valid(form)
class PaymentDetailsView(CheckCountryPreCondition, OscarPaymentDetailsView):
def handle_payment(self, order_number, order_total, **kwargs):
method = self.checkout_session.payment_method()
if method == 'cod':
return self.handle_cod_payment(order_number, order_total, **kwargs)
elif method == 'custom_payment':
return self.handle_custom_payment_payment(order_number, order_total, **kwargs)
else:
raise PaymentError(_('Bad payment method in handle_payment!'))
def handle_cod_payment(self, order_number, total, **kwargs):
reference = cod_gateway.create_transaction(order_number, total)
source_type, is_created = SourceType.objects.get_or_create(
name='Cash on delivery')
source = Source(
source_type=source_type,
currency=total.currency,
amount_allocated=total.incl_tax,
amount_debited=total.incl_tax
)
self.add_payment_source(source)
self.add_payment_event('awaiting-delivery', total.incl_tax, reference=reference)
def handle_custom_payment_payment(self, order_number, total, **kwargs):
submission = self.build_submission(order_number=order_number, **kwargs)
# Save required payment gateway data
# also validates that we have all we need
custom_payment_payment_data = facade.get_gateway_url_and_parameters(
submission, self.request.build_absolute_uri, live=settings.custom_payment['LIVE'])
# Any raised exceptions are handled by the PaymentDetail.submit() code.
custom_payment_order = facade.create_order(
order_number=order_number,
amount=total,
payment_data=custom_payment_payment_data
)
# record payment data to session to double verify things on success / failure
self.set_custom_payment_payment_data(custom_payment_payment_data, custom_payment_order)
source = Source(
source_type=facade.get_source_type(),
currency=total.currency,
amount_allocated=total.incl_tax, # amount_* field depends on type of transaction.
reference=custom_payment_order.id
)
self.add_payment_source(source)
# Also record payment event.
# This will be visible in the Dashboard
self.add_payment_event('pre-auth', total.incl_tax, reference=custom_payment_order.id)
# Regardless of whether the order is paid, write it in the database before redirecting.
# Oscar actually skips this when redirecting the user to the payment provider.
self._save_order(order_number, submission)
# Redirect the user to the payment provider's gateway.
raise RedirectRequired(facade.get_payment_url())
def _save_order(self, order_number, submission):
# Finalize the order that PaymentDetailsView.submit() started
# If all is ok with payment, try and place order
logger.info("Order #%s: payment started, placing order", order_number)
try:
# Call OrderPlacementMixin.handle_order_placement()
return self.handle_order_placement(
order_number, submission['user'], submission['basket'],
submission['shipping_address'], submission['shipping_method'],
submission['shipping_charge'], submission['billing_address'],
submission['order_total'], **(submission['order_kwargs'])
)
except UnableToPlaceOrder as e:
# It's possible that something will go wrong while trying to
# actually place an order. Not a good situation to be in as a
# payment transaction may already have taken place, but needs
# to be handled gracefully.
logger.error("Order #%s: unable to place order - %s", order_number, e, exc_info=True)
msg = six.text_type(e)
self.restore_frozen_basket()
return self.render_to_response(self.get_context_data(error=msg))
# Can't update CheckoutSessionMixin nicely without causing trouble,
# hence dumping the overridden / new methods from that class here
def check_payment_data_is_captured(self, request):
# We don't collect payment data by default so we don't have anything to
# validate here. If your shop requires forms to be submitted on the
# payment details page, then override this method to check that the
# relevant data is available. Often just enforcing that the preview
# view is only accessible from a POST request is sufficient.
if not self.checkout_session.payment_method():
raise FailedPreCondition(
url=reverse('checkout:payment-method'),
message=_("Please select a payment method for your order!")
)
def set_custom_payment_payment_data(self, payment_data, custom_payment_order):
self.request.session['custom_payment'] = payment_data
self.request.session['custom_payment_order_id'] = custom_payment_order.id
self.request.session['ongoing_online_payment'] = True
self.request.session.pop('custom_payment_error', None)
def get_context_data(self, **kwargs):
ctx = super(PaymentDetailsView, self).get_context_data(**kwargs)
ctx.update({'payment_method': self.checkout_session.payment_method()})
return ctx
def send_confirmation_message(self, order, *args, **kwargs):
# In case of custom_payment, delay sending the order confirmation till payment success!
if not self.checkout_session.payment_method() == 'custom_payment':
super(PaymentDetailsView, self).send_confirmation_message(order, *args, **kwargs)
You may ignore the details for custom_payment since that is something specific for my project / payment gateway, you would likely have different requirements for that. I have still left a chunk of the things in it so that you may think of the things accordingly.
PS - Please note I'm using Python3.6, so a few things (like super) might not work as mentioned here, in previous Python versions (specially 2.7)
Update
My templates/checkout/payment_method.html:
{% extends "checkout/checkout.html" %}
{% load i18n %}
{% block checkout_title %}{{ form.payment_method.label }}{% endblock %}
{% block checkout_nav %}
{% include 'checkout/nav.html' with step=3 %}
{% endblock %}
{% block content %}
{% if error %}
<div class="alert alert-error">
{{ error }}
</div>
{% endif %}
<form action="" method="post">
{% csrf_token %}
{% if form.payment_method.errors %}{{ form.payment_method.errors }}{% endif %}
<div class="radio">
<label for="id_payment_method_0">
<input id="id_payment_method_0" name="payment_method" type="radio" value="cod" required="">{% trans "Cash on delivery" %}
</label>
</div>
<div class="radio">
<label for="id_payment_method_1">
<input id="id_payment_method_1" name="payment_method" type="radio" value="custom_payment" required="">{% trans "Credit / Debit Card" %}
</label>
</div>
<p><button type="submit" class="btn btn-large btn-primary">{% trans "Continue to confirmation" %}</button>
</form>
{% endblock %}
Related
I have a Flask App that passes user input validation when it should fail. I have similar code in another part of the app that works just fine. It seems like the FileAllowed() method is not being called. Or if it is, it's returning true.
This code uploads a user file to s3.
The MultipleFileField() method has a validation check for only image file extensions. However, any file passes this check. The InputRequired() method works just fine.
I've tried multiple variations of this and nothing has worked. It's not a CRSF issue because other routes with similar code work without it.
flask_wtf Form:
class AddImgForm(FlaskForm): # should use InputRequired() not DataRequired()
images= MultipleFileField('Upload Images', validators=[InputRequired(),FileAllowed(['jpg', 'png', 'jpeg', 'tif'])])
submitBTN2 = SubmitField('Upload')
Route:
#users.route("/account", methods=['GET', 'POST'])
#login_required
def account():
form = UpdateAccountForm()
if form.validate_on_submit():
if form.picture.data: # if a picture is provided save picture
picture_file= save_picture(form.picture.data, 'p') # saves picture and returns dict with ['filepath'] and ['filename']
BUCKET= os.environ['BUCKET'] # should send to 'bucket-publicaccess/uploads' bucket in production
s3= boto3.resource("s3",
region_name = "us-east-2", # had to add "us-east-2" as incorrect region was generated
config= boto3.session.Config(signature_version='s3v4'), # must add this to address newer security
aws_access_key_id = os.environ["AWS_ACCESS_KEY_ID"],
aws_secret_access_key = os.environ["AWS_SECRET_ACCESS_KEY"]) # AWS Generated key pairs
s3.Bucket(BUCKET).upload_file(picture_file['filepath'], 'uploads/'+ picture_file['filename']) #upload to s3
current_user.image_file= 'uploads/'+picture_file['filename']
print(current_user.image_file)
os.remove(picture_file['filepath']) # remove file from tmp directory
current_user.username = form.username.data
current_user.email = form.email.data
db.session.commit() # commit changes
flash('Your account has been updated!', 'success')
return redirect(url_for('users.account'))
elif request.method == 'GET':
form.username.data = current_user.username
form.email.data = current_user.email
image_file = current_user.image_file
return render_template('account.html', title='Account',
image_file=image_file, form=form)
HTML:
<form method="POST" action="" enctype="multipart/form-data" id="addImgForm">
{{ addImgForm.hidden_tag() }}
<fieldset class="form-group">
<div class="form-group">
{{ addImgForm.images.label() }}
{{ addImgForm.images(class="form-control-file") }}
{% if addImgForm.images.errors %}
{% for error in addImgForm.images.errors %}
<span class="text-danger">{{ error }}</span></br>
{% endfor %}
{% endif %}
</div>
<div class="form-group">
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
{{ addImgForm.submitBTN2(class="btn btn-outline-info") }}
</div>
</fieldset>
</form>
Any help would be appreciated as most questions are about this failing while this code always passes.
The problem is with the FileAllowed validator, it is expecting a single instance of FileStorage when validating, whereas MultipleFileField passes a list of FileStorage instances to the validator. You can overcome this by writing your own validator, for example:
class MultiFileAllowed(object):
def __init__(self, upload_set, message=None):
self.upload_set = upload_set
self.message = message
def __call__(self, form, field):
# FileAllowed only expects a single instance of FileStorage
# if not (isinstance(field.data, FileStorage) and field.data):
# return
# Check that all the items in field.data are FileStorage items
if not (all(isinstance(item, FileStorage) for item in field.data) and field.data):
return
for data in field.data:
filename = data.filename.lower()
if isinstance(self.upload_set, Iterable):
if any(filename.endswith('.' + x) for x in self.upload_set):
return
raise StopValidation(self.message or field.gettext(
'File does not have an approved extension: {extensions}'
).format(extensions=', '.join(self.upload_set)))
if not self.upload_set.file_allowed(field.data, filename):
raise StopValidation(self.message or field.gettext(
'File does not have an approved extension.'
))
A simple single file example using Flask, Flask-WTF and Flask-Boostrap:
from collections import Iterable
from flask_bootstrap import Bootstrap
from flask import Flask, redirect, url_for, render_template_string
from flask_wtf import FlaskForm
from flask_wtf.file import FileAllowed
from markupsafe import Markup
from werkzeug.datastructures import FileStorage
from wtforms.fields import MultipleFileField, SubmitField
from wtforms.validators import InputRequired, StopValidation
app = Flask(__name__)
app.config['SECRET_KEY'] = '123456790'
Bootstrap(app)
class MultiFileAllowed(object):
def __init__(self, upload_set, message=None):
self.upload_set = upload_set
self.message = message
def __call__(self, form, field):
if not (all(isinstance(item, FileStorage) for item in field.data) and field.data):
return
for data in field.data:
filename = data.filename.lower()
if isinstance(self.upload_set, Iterable):
if any(filename.endswith('.' + x) for x in self.upload_set):
return
raise StopValidation(self.message or field.gettext(
'File does not have an approved extension: {extensions}'
).format(extensions=', '.join(self.upload_set)))
if not self.upload_set.file_allowed(field.data, filename):
raise StopValidation(self.message or field.gettext(
'File does not have an approved extension.'
))
class ImagesForm(FlaskForm):
images = MultipleFileField(
'Upload Images',
validators=[
InputRequired(),
MultiFileAllowed(['jpg', 'png', 'jpeg', 'tif'])
]
)
submit = SubmitField('Upload')
upload_template = '''
{% import "bootstrap/wtf.html" as wtf %}
<form method="POST" enctype="multipart/form-data">
{{ wtf.quick_form(form) }}
</form>
'''
#app.route('/')
def index():
return Markup("<a href='uploads'>Go to the uploads<a>")
#app.route('/uploads', methods=['GET', 'POST'])
def upload():
form = ImagesForm()
if form.validate_on_submit():
if form.images:
for image in form.images.data:
print 'Uploaded File: {}'.format(image.filename)
return redirect(url_for('index'))
else:
print form.errors
return render_template_string(upload_template, form=form)
if __name__ == '__main__':
app.run()
Thanks for the above! I had to change to
class MultiFileAllowed(object):
def __init__(self, upload_set, message=None):
self.upload_set = upload_set
self.message = message
def __call__(self, form, field):
if not (all(isinstance(item, FileStorage) for item in field.data) and field.data):
return
for data in field.data:
filename = data.filename.lower()
if isinstance(self.upload_set, Iterable):
print(filename, flush=True)
print(any(filename.endswith("." + x) for x in self.upload_set), flush=True)
if not any(filename.endswith("." + x) for x in self.upload_set):
raise StopValidation(
self.message
or field.gettext("File does not have an approved extension: {extensions}").format(
extensions=", ".join(self.upload_set)
)
)
The admin actions seem to work on several items selected in the list view of django admin interface:
In my case I would like to have a simple action button on the change (one item) view.
Is there a way to make the django admin actions available there?
I know that I can walk around this problem by going to the list view, and select one item there. But it would be more nice to have it directly available.
Create a template for your model in your app.
templates/admin/<yourapp>/<yourmodel>/change_form.html
With this example content to add a button when changing an existing object.
{% extends "admin/change_form.html" %}
{% block submit_buttons_bottom %}
{{ block.super }}
{% if original %} {# Only show if changing #}
<div class="submit-row">
<a href="{% url 'custom-model-action' original.pk %}">
Another action
</a>
</div>
{% endif %}
{% endblock %}
Link that action to any url and redirect back to your model change object view. More information about extending admin templates.
Update: Added complete common use case for custom action on existing object
urls.py
urlpatterns = [
url(r'^custom_model_action/(?P<object_pk>\d+)/$',
core_views.custom_model_action, name='custom-model-action')
]
views.py
from django.urls import reverse
from django.contrib import messages
from django.http import HttpResponse, HttpResponseRedirect
def custom_model_action(request, object_pk):
messages.info(request, 'Performed custom action!')
return HttpResponseRedirect(
reverse('admin:<yourapp>_<yourmodel>_change', args=[object_pk])
)
If you realy need per-single object, I suggest you to use this solution, eg:
class Gallery(TimeStampedModel):
title = models.CharField(max_length=200)
attachment = models.FileField(upload_to='gallery/attachment/%Y/%m/%d')
def __str__(self):
return self.title
def process_button(self):
return ('<button id="%(id)s class="btn btn-default process_btn" '
'data-value="%(value)s>Process</button>' % {'id': self.pk, 'value': self.attachment.url})
process_button.short_description = 'Action'
process_button.allow_tags = True
In your admin.py, insert process_button into list_display;
class GalleryAdmin(admin.ModelAdmin):
list_display = ['title', 'process_button', 'created']
search_fields = ['title', 'pk']
....
class Media:
js = ('path/to/yourfile.js', )
Then, inside yourfile.js, you can also process it..
$('.process_btn').click(function(){
var id = $(this).attr('id'); // single object id
var value = $(this).data('value'); // single object value
...
});
Hope it helpful..
Not the same as the topic starter asked, but this snippet allows to have Single Object action from on the list page with minimum amount of code
BaseAction code
class AdminActionError(Exception):
pass
class AdminObjectAction:
"""Base class for Django Admin actions for single object"""
short_description = None
exp_obj_state = {}
def __init__(self, modeladmin, request, queryset):
self.admin = modeladmin
self.request = request
self.queryset = queryset
self.__call__()
def validate_qs(self):
count = self.queryset.count()
if count != 1:
self.error("You must select one object for this action.")
if self.exp_obj_state:
if self.queryset.filter(**self.exp_obj_state).count() != 1:
self.error(f'Selected object does not meet the requirements: {self.exp_obj_state}')
def error(self, msg):
raise AdminActionError(msg)
def get_object(self):
return self.queryset.get()
def process_object_action(self, obj):
pass
def validate_obj(self, obj):
pass
def __call__(self, *args, **kwargs):
try:
self.validate_qs()
obj = self.get_object()
self.validate_obj(obj)
except AdminActionError as e:
self.admin.message_user(self.request, f"Failed: {e}", level=messages.ERROR)
else:
with transaction.atomic():
result = self.process_object_action(obj)
self.admin.message_user(self.request, f"Success: {self.short_description}, {result}")
Custom Action [minimum amount of code]
class RenewSubscriptionAction(AdminObjectAction):
short_description = 'Renew subscription'
exp_obj_state = {
'child': None,
'active_status': True,
}
def process_object_action(self, obj):
manager = RenewManager(user=obj.user, subscription=obj)
return manager.process()
AdminClass
class SomeAdmin(admin.ModelAdmin):
actions = [RenewSubscriptionAction]
The built-in admin actions operate on a queryset.
You can use a calable for the action you whant or to show something else:
class ProductAdmin(admin.ModelAdmin):
list_display ('name' )
readonly_fields('detail_url)
def detail_url(self, instance):
url = reverse('product_detail', kwargs={'pk': instance.slug})
response = format_html("""{0}""", product_detail)
return response
or using forms
class ProductForm(forms.Form):
name = forms.Charfield()
def form_action(self, product, user):
return Product.value(
id=product.pk,
user= user,
.....
)
#admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
# render buttons and links to
def product_actions(self, obj):
return format_html(
'<a class="button" href="{}">Action1</a> '
'<a class="button" href="{}">Action 2</a>',
reverse('admin:product-action-1', args=[obj.pk]),
reverse('admin:aproduct-action-3', args=[obj.pk]),
)
for more details about using forms
What I am trying to do is the following: User will have a production (also known as podcast episode) already created with the necessary info until this point (production_id would be the id for this query). The idea is, when user arrives to ChapterMark template, he would be able to create several timestamps to point out certain topics he/she is talking throughout his/her episode. chaptermark_id is created since it would be a One-To-Many and with this id I can add as much timestamps as I want within that episode. With this in mind, which is the best approach for this type of situation and how I can implement it in my form, class view and template?
Thanks in advance
Here is my views.py:
from django.http import HttpResponseRedirect, Http404, HttpResponseForbidden
from django.shortcuts import render, get_object_or_404
from django.views.generic import View, RedirectView, TemplateView
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from .forms.client_setup import ClientSetupForm
from .forms.podcast_setup import PodcastSetupForm
from .forms.episode_info import EpisodeInfoForm
from .forms.image_files import EpisodeImageFilesForm
from .forms.wordpress_info import EpisodeWordpressInfoForm
from .forms.chapter_marks import EpisodeChapterMarksForm
from .forms.show_links import ShowLinksForm
from .forms.tweetables import TweetablesForm
from .forms.clicktotweet import ClickToTweetForm
from .forms.schedule import ScheduleForm
from .forms.wordpress_account import WordpressAccountForm
from .forms.wordpress_account_setup import WordpressAccountSetupForm
from .forms.wordpress_account_sortable import WordpressAccountSortableForm
from .forms.soundcloud_account import SoundcloudAccountForm
from .forms.twitter_account import TwitterAccountForm
from producer.helpers import get_podfunnel_client_and_podcast_for_user
from producer.helpers.soundcloud_api import SoundcloudAPI
from producer.helpers.twitter import TwitterAPI
from django.conf import settings
from producer.models import Client, Production, ChapterMark, ProductionLink, ProductionTweet, Podcast, WordpressConfig, Credentials, WordPressSortableSection, \
TwitterConfig, SoundcloudConfig
from django.core.urlresolvers import reverse
from producer.tasks.auphonic import update_or_create_preset_for_podcast
class EpisodeChapterMarksView(LoginRequiredMixin, View):
form_class = EpisodeChapterMarksForm
template_name = 'fc/forms_chapter_marks.html'
def get(self, request, *args, **kwargs):
initial_values = {}
user = request.user
# Lets get client and podcast for the user already. if not existent raise 404
client, podcast = get_fc_client_and_podcast_for_user(user)
if client is None or podcast is None:
raise Http404
# The production_id or the chaptermark_id must be passed on teh KWargs
production_id = kwargs.get('production_id', None)
chaptermark_id = kwargs.get('chaptermark_id', None)
if chaptermark_id:
chaptermark = get_object_or_404(ChapterMark, id=chaptermark_id)
production = chaptermark.production
elif production_id:
production = get_object_or_404(Production, id=production_id)
chaptermark = None
initial_values['production_id'] = production.id
if chaptermark is not None:
initial_values['chaptermark_id'] = chaptermark_id
initial_values['start_time'] = chaptermark.start_time
initial_values['title'] = chaptermark.title
form = self.form_class(initial=initial_values)
return render(request, self.template_name, {'form': form})
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
# lets get the data
production_id = form.cleaned_data.get('production_id')
chaptermark_id = form.cleaned_data.get('chaptermark_id')
start_time = form.cleaned_data.get('start_time')
title = form.cleaned_data.get('title')
# Get production
production = get_object_or_404(Production, id=production_id)
# if a chaptermark existed, we update, if not we create
if chaptermark_id is not None:
chaptermark = ChapterMark.objects.get(id=chaptermark_id)
else:
chaptermark = ChapterMark()
chaptermark.start_time = start_time
chaptermark.title = title
chaptermark.production = production
chaptermark.save()
return HttpResponseRedirect(reverse('fc:episodeshowlinks'))
return render(request, self.template_name, {'form': form})
chaptermark.py form:
from django import forms
class EpisodeChapterMarksForm(forms.Form):
production_id = forms.IntegerField(widget=forms.Field.hidden_widget, required=False)
chaptermark_id = forms.IntegerField(widget=forms.Field.hidden_widget, required=False)
start_time = forms.TimeField(required=False)
title = forms.CharField(max_length=200)
chaptermark template:
{% extends "fc/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="progress">
<div class="progress-bar progress-bar-striped progress-bar-success active" role="progressbar" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100" style="width: 50%">
<span class="sr-only">50% Complete</span>
</div>
</div>
<div class="panel panel-default box-shadow--16dp col-sm-6 col-sm-offset-3">
<div class="panel-body">
<div class='row'>
<div class='col-sm-12'>
{% if title %}
<h1 class='{% if title_align_center %}text-align-center{% endif %}'>{{ title }}<!-- : {{ get.clientsetup.company_name }} --></h1>
{% endif %}
{% if subtitle %}
<h3 class='{% if subtitle_align_center %}text-align-center{% endif %}'>{{ subtitle }}</h4>
{% endif %}
<h5>Chapter Marks</h5>
<form method='POST' action=''>{% csrf_token %}
{{ form|crispy }}
<hr/>
<button type="submit" class="btn btn-primary box-shadow--6dp"><i class="fa fa-chevron-right pull-right"></i> Continue
</button>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
----------------------UPDATE-------------------------
Was in views.py:
#login_required
def episodechaptermarks(request):
title = 'Podcast'
title_align_center = True
subtitle = 'Setup | Add Episode'
subtitle_align_center = True
form = ChapterMarksForm(request.POST or None)
context = {
"title": title,
"subtitle": subtitle,
"form": form
}
if form.is_valid():
instance = form.save(commit=False)
start_time = form.cleaned_data.get("start_time")
title = form.cleaned_data.get("title")
instance.start_time = start_time
instance.title = title
instance.user = request.user
instance.save()
return render(request, "pod_funnel/forms_chapter_marks.html", context)
else:
return render(request, "pod_funnel/forms_chapter_marks.html", context)
ModelForm:
from django import forms
from producer.models import ChapterMark
class ChapterMarksForm(forms.ModelForm):
class Meta:
model = ChapterMark
fields = ['start_time', 'title']
def clean_start_time(self):
start_time = self.cleaned_data.get('start_time')
return start_time
def clean_title(self):
title = self.cleaned_data.get('title')
return title
In essence, your production object has a series of timestamps that relate back via a FK. You need a set of views for CRUD at the production level. Let's assume your models are already created. There's a few things from my experience I want to point out that I think will point you in the right direction.
Unless absolutely necessary never use a Form class when creating a form object that mirrors a model; you are introducing a need for unnecessary complexity and opening the door for errors. Use a ModelForm, which can save objects to the DB straight from the view and help you manage cleaning, validation, and more. In addition, these can easily mesh with generic views of all sorts.
For this sort of relation (a model object with a varying number of model objects of a given type relating back that object) Django provides the powerful but difficult inlineformset_factory. This creates a series of inline forms as needed for a relation such as this.
So you have a model (production) and another related back to that (timestamp). You need to save these at the same time, possibly perform cleaning or validation, and really provide CRUD functionality for this relationship as a whole. For this, you could create a complex view from scratch or you could use django-extra-views and their generic CBVs for models with inlines. You can subclass CreateWithInlinesView, UpdateWithInlinesView. Why? Most Django devs would agree formsets are difficult to implement.
So, to give you a simplified version of how you can do this
from extra_views.advanced import CreateWithInlinesView, InlineFormSet, UpdateWithInlinesView
class TimeStampsInline(InlineFormSet):
model = models.TimeStamp
form = TimeStampForm # If you haven't created a custom ModelForm, can also specify "fields= ['field_1','field_2',...] and the CBV will create a ModelForm
extra = 0
class ProductionCreate(CreateWithInlinesView):
model=models.Production
inlines = [TimeStampsInline]
success_url = reverse('production-list') # the url to return to on successful create
exclude = ['created_by'] # render all fields except this in form
template_name = 'myapp/update.html'
class ProductionUpdate(UpdateWithInlinesView):
model=models.Production
inlines = [TimeStampsInline]
success_url = reverse('production-list')
exclude = ['created_by']
template_name = 'myapp/update.html'
Your template(s) will have to be built in specification with formsets; there's documentation and tutorials all over for that.
That's already a lot to digest, but you probably get the general idea. Don't build a horse from scratch ;)
If I have two forms:
class ContactForm(forms.Form):
name = forms.CharField()
message = forms.CharField(widget=forms.Textarea)
class SocialForm(forms.Form):
name = forms.CharField()
message = forms.CharField(widget=forms.Textarea)
and wanted to use a class based view, and send both forms to the template, is that even possible?
class TestView(FormView):
template_name = 'contact.html'
form_class = ContactForm
It seems the FormView can only accept one form at a time.
In function based view though I can easily send two forms to my template and retrieve the content of both within the request.POST back.
variables = {'contact_form':contact_form, 'social_form':social_form }
return render(request, 'discussion.html', variables)
Is this a limitation of using class based view (generic views)?
Many Thanks
Here's a scaleable solution. My starting point was this gist,
https://gist.github.com/michelts/1029336
i've enhanced that solution so that multiple forms can be displayed, but either all or an individual can be submitted
https://gist.github.com/jamesbrobb/748c47f46b9bd224b07f
and this is an example usage
class SignupLoginView(MultiFormsView):
template_name = 'public/my_login_signup_template.html'
form_classes = {'login': LoginForm,
'signup': SignupForm}
success_url = 'my/success/url'
def get_login_initial(self):
return {'email':'dave#dave.com'}
def get_signup_initial(self):
return {'email':'dave#dave.com'}
def get_context_data(self, **kwargs):
context = super(SignupLoginView, self).get_context_data(**kwargs)
context.update({"some_context_value": 'blah blah blah',
"some_other_context_value": 'blah'})
return context
def login_form_valid(self, form):
return form.login(self.request, redirect_url=self.get_success_url())
def signup_form_valid(self, form):
user = form.save(self.request)
return form.signup(self.request, user, self.get_success_url())
and the template looks like this
<form class="login" method="POST" action="{% url 'my_view' %}">
{% csrf_token %}
{{ forms.login.as_p }}
<button name='action' value='login' type="submit">Sign in</button>
</form>
<form class="signup" method="POST" action="{% url 'my_view' %}">
{% csrf_token %}
{{ forms.signup.as_p }}
<button name='action' value='signup' type="submit">Sign up</button>
</form>
An important thing to note on the template are the submit buttons. They have to have their 'name' attribute set to 'action' and their 'value' attribute must match the name given to the form in the 'form_classes' dict. This is used to determine which individual form has been submitted.
By default, class-based views only support a single form per view. But there are other ways to accomplish what you need. But again, this cannot handle both forms at the same time. This will also work with most of the class-based views as well as regular forms.
views.py
class MyClassView(UpdateView):
template_name = 'page.html'
form_class = myform1
second_form_class = myform2
success_url = '/'
def get_context_data(self, **kwargs):
context = super(MyClassView, self).get_context_data(**kwargs)
if 'form' not in context:
context['form'] = self.form_class(request=self.request)
if 'form2' not in context:
context['form2'] = self.second_form_class(request=self.request)
return context
def get_object(self):
return get_object_or_404(Model, pk=self.request.session['value_here'])
def form_invalid(self, **kwargs):
return self.render_to_response(self.get_context_data(**kwargs))
def post(self, request, *args, **kwargs):
self.object = self.get_object()
if 'form' in request.POST:
form_class = self.get_form_class()
form_name = 'form'
else:
form_class = self.second_form_class
form_name = 'form2'
form = self.get_form(form_class)
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(**{form_name: form})
template
<form method="post">
{% csrf_token %}
.........
<input type="submit" name="form" value="Submit" />
</form>
<form method="post">
{% csrf_token %}
.........
<input type="submit" name="form2" value="Submit" />
</form>
Its is possible for one class-based view to accept two forms at a time.
view.py
class TestView(FormView):
template_name = 'contact.html'
def get(self, request, *args, **kwargs):
contact_form = ContactForm()
contact_form.prefix = 'contact_form'
social_form = SocialForm()
social_form.prefix = 'social_form'
# Use RequestContext instead of render_to_response from 3.0
return self.render_to_response(self.get_context_data({'contact_form': contact_form, 'social_form': social_form}))
def post(self, request, *args, **kwargs):
contact_form = ContactForm(self.request.POST, prefix='contact_form')
social_form = SocialForm(self.request.POST, prefix='social_form ')
if contact_form.is_valid() and social_form.is_valid():
### do something
return HttpResponseRedirect(>>> redirect url <<<)
else:
return self.form_invalid(contact_form,social_form , **kwargs)
def form_invalid(self, contact_form, social_form, **kwargs):
contact_form.prefix='contact_form'
social_form.prefix='social_form'
return self.render_to_response(self.get_context_data({'contact_form': contact_form, 'social_form': social_form}))
forms.py
from django import forms
from models import Social, Contact
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit, Button, Layout, Field, Div
from crispy_forms.bootstrap import (FormActions)
class ContactForm(forms.ModelForm):
class Meta:
model = Contact
helper = FormHelper()
helper.form_tag = False
class SocialForm(forms.Form):
class Meta:
model = Social
helper = FormHelper()
helper.form_tag = False
HTML
Take one outer form class and set action as TestView Url
{% load crispy_forms_tags %}
<form action="/testview/" method="post">
<!----- render your forms here -->
{% crispy contact_form %}
{% crispy social_form%}
<input type='submit' value="Save" />
</form>
Good Luck
I have used a following generic view based on TemplateView:
def merge_dicts(x, y):
"""
Given two dicts, merge them into a new dict as a shallow copy.
"""
z = x.copy()
z.update(y)
return z
class MultipleFormView(TemplateView):
"""
View mixin that handles multiple forms / formsets.
After the successful data is inserted ``self.process_forms`` is called.
"""
form_classes = {}
def get_context_data(self, **kwargs):
context = super(MultipleFormView, self).get_context_data(**kwargs)
forms_initialized = {name: form(prefix=name)
for name, form in self.form_classes.items()}
return merge_dicts(context, forms_initialized)
def post(self, request):
forms_initialized = {
name: form(prefix=name, data=request.POST)
for name, form in self.form_classes.items()}
valid = all([form_class.is_valid()
for form_class in forms_initialized.values()])
if valid:
return self.process_forms(forms_initialized)
else:
context = merge_dicts(self.get_context_data(), forms_initialized)
return self.render_to_response(context)
def process_forms(self, form_instances):
raise NotImplemented
This has the advantage that it is reusable and all the validation is done on the forms themselves.
It is then used as follows:
class AddSource(MultipleFormView):
"""
Custom view for processing source form and seed formset
"""
template_name = 'add_source.html'
form_classes = {
'source_form': forms.SourceForm,
'seed_formset': forms.SeedFormset,
}
def process_forms(self, form_instances):
pass # saving forms etc
It is not a limitation of class-based views. Generic FormView just is not designed to accept two forms (well, it's generic). You can subclass it or write your own class-based view to accept two forms.
Use django-superform
This is a pretty neat way to thread a composed form as a single object to outside callers, such as the Django class based views.
from django_superform import FormField, SuperForm
class MyClassForm(SuperForm):
form1 = FormField(FormClass1)
form2 = FormField(FormClass2)
In the view, you can use form_class = MyClassForm
In the form __init__() method, you can access the forms using: self.forms['form1']
There is also a SuperModelForm and ModelFormField for model-forms.
In the template, you can access the form fields using: {{ form.form1.field }}. I would recommend aliasing the form using {% with form1=form.form1 %} to avoid rereading/reconstructing the form all the time.
Resembles #james answer (I had a similar starting point), but it doesn't need to receive a form name via POST data. Instead, it uses autogenerated prefixes to determine which form(s) received POST data, assign the data, validate these forms, and finally send them to the appropriate form_valid method. If there is only 1 bound form it sends that single form, else it sends a {"name": bound_form_instance} dictionary.
It is compatible with forms.Form or other "form behaving" classes that can be assigned a prefix (ex. django formsets), but haven't made a ModelForm variant yet, tho you could use a model form with this View (see edit below). It can handle forms in different tags, multiple forms in one tag, or a combination of both.
The code is hosted on github (https://github.com/AlexECX/django_MultiFormView). There are some usage guidelines and a little demo covering some use cases. The goal was to have a class that feels as close as possible like the FormView.
Here is an example with a simple use case:
views.py
class MultipleFormsDemoView(MultiFormView):
template_name = "app_name/demo.html"
initials = {
"contactform": {"message": "some initial data"}
}
form_classes = [
ContactForm,
("better_name", SubscriptionForm),
]
# The order is important! and you need to provide an
# url for every form_class.
success_urls = [
reverse_lazy("app_name:contact_view"),
reverse_lazy("app_name:subcribe_view"),
]
# Or, if it is the same url:
#success_url = reverse_lazy("app_name:some_view")
def get_contactform_initial(self, form_name):
initial = super().get_initial(form_name)
# Some logic here? I just wanted to show it could be done,
# initial data is assigned automatically from self.initials anyway
return initial
def contactform_form_valid(self, form):
title = form.cleaned_data.get('title')
print(title)
return super().form_valid(form)
def better_name_form_valid(self, form):
email = form.cleaned_data.get('email')
print(email)
if "Somebody once told me the world" is "gonna roll me":
return super().form_valid(form)
else:
return HttpResponse("Somebody once told me the world is gonna roll me")
template.html
{% extends "base.html" %}
{% block content %}
<form method="post">
{% csrf_token %}
{{ forms.better_name }}
<input type="submit" value="Subscribe">
</form>
<form method="post">
{% csrf_token %}
{{ forms.contactform }}
<input type="submit" value="Send">
</form>
{% endblock content %}
EDIT - about ModelForms
Welp, after looking into ModelFormView I realised it wouldn't be that easy to create a MultiModelFormView, I would probably need to rewrite SingleObjectMixin as well. In the mean time, you can use a ModelForm as long as you add an 'instance' keyword argument with a model instance.
def get_bookform_form_kwargs(self, form_name):
kwargs = super().get_form_kwargs(form_name)
kwargs['instance'] = Book.objects.get(title="I'm Batman")
return kwargs
I have a function, that if i run from django shell, it populates my db succesfully.
I have three classes.
Class League(models.Model):
LeagueName = models.CharField()
#class League explained below
Class Fixture(models.Model):
League = models.ForeignKey(League)
home_team = models.ForeginKey(Team)
away_team = models.ForeginKey(Team)
Class Teams(models.Model):
League = models.ForeignKey(League)
I want the functionality to able to calculate the fixture table with respect to only one league. Here is what I am doing now. which at the moment it is not doing. How to?
class League(models.Model):
LeagueName = models.CharField(max_length=200)
FixturesGenerated = models.BooleanField(default=False)
def __unicode__(self):
return self.LeagueName
def CreateFixtures(self, print_only=True):
if self.FixturesGenerated==True:
return
from dateutil import rrule
from dateutil.relativedelta import *
from League.models import Team, Fixture
import itertools
import datetime
import random
"""
Instead of your array I will use actual objects from the Teams model
"""
teams = Team.objects.all()
fixcombos = list(itertools.combinations(teams,2))
random.shuffle(fixcombos)
nofixtures = len(fixcombos)
datestart = datetime.date.today()
dateend = datestart + datetime.timedelta(days=125)
#calculate possible fixture dates,
fixdays = list(rrule.rrule(rrule.DAILY, byweekday=(rrule.SA,rrule.SU), dtstart=datestart, until=dateend))
nofmatchdays = len(fixdays)
# perday = perday matches, and then moved it into array for for loop of dates available.
perday = nofixtures/nofmatchdays +1
perday = range(0,perday)
#for loop to extend the fixture days array to repeat dates.
for x in perday:
fixdays = fixdays + fixdays
fixarray = range(0, nofixtures)
# for loop for printing the possible functions
# this for loop number will give the database entry id number of a particular name. we still have to do some manipulation.
result = ''
for y in fixarray:
printline = 'Date: ' + str(fixdays[y]) + ': Teams Involved: ' + str(fixcombos[y])
result += printline
# now the print array functionality needs to be replaced with p.save() functionality in the final program.
"""
Code to actually save the fixture if print_only is not True
"""
if not print_only:
f = Fixture()
f.league = self
f.fixture_date = fixdays[y]
f.team_one = fixcombos[y][0]
f.team_two = fixcombos[y][1]
f.save()
self.FixturesGenerated = True
self.save()
[EDIT] To furthur elaborate, here is my admin.py
from League.models import League
from League.models import Team
from League.models import Fixture
from django.contrib import admin
from django.http import HttpResponseRedirect
class ButtonableModelAdmin(admin.ModelAdmin):
buttons=()
def change_view(self, request, object_id, extra_context={}):
extra_context['buttons']=self.buttons
return super(ButtonableModelAdmin, self).change_view(request, object_id, extra_context)
def button_view_dispatcher(self, request, object_id, command):
obj = self.model._default_manager.get(pk=object_id)
return getattr(self, command)(request, obj) \
or HttpResponseRedirect(request.META['HTTP_REFERER'])
def get_urls(self):
from django.conf.urls.defaults import patterns, url
from django.utils.functional import update_wrapper
def wrap(view):
def wrapper(*args, **kwargs):
return self.admin_site.admin_view(view)(*args, **kwargs)
return update_wrapper(wrapper, view)
info = self.model._meta.app_label, self.model._meta.module_name
return patterns('',
*(url(r'^(\d+)/(%s)/$' % but[0], wrap(self.button_view_dispatcher)) for but in self.buttons)
) + super(ButtonableModelAdmin, self).get_urls()
class TeamsInLeague(admin.StackedInline):
model = Team
extra = 1
class FixturesInLeague(admin.TabularInline):
model = Fixture
extra = 0
class LeagueAdmin(ButtonableModelAdmin):
fields = ['LeagueName', 'FixturesGenerated']
inlines = [TeamsInLeague, FixturesInLeague, ]
def gen_fixtures(self, request, obj):
obj.CreateFixtures(print_only=False)
gen_fixtures.short_description = 'Generate Fixtures'
gen_fixtures.url = "gen_fixtures"
buttons = [ (gen_fixtures.func_name, gen_fixtures.short_description) ]
admin.site.register(League,LeagueAdmin)
admin.site.register(Team)
admin.site.register(Fixture)
and here it is from my templates/../change_form.html.
{% extends "admin/change_form.html" %}
{% block object-tools %}
{% if change %}{% if not is_popup %}
<ul class="object-tools">
{% for button in buttons %}
<li>{{ button.1 }}</li>
{% endfor %}
<li>History ala bala</li>
{% if has_absolute_url %}<li>View on site</li>{% endif%}
</ul>
{% endif %}{% endif %}
{% endblock %}
thankyou.
//mouse
My suggestion (after doing my best to understand what you are trying to do) is to make your anchors use a get parameter of your team id.
{% for button in buttons %}
<li>{{ button.1 }}</li>
{% endfor %}
And then in your view you could pull off the team ID from the request and call CreateFixtures.
team = request.get('team_id')
league.CreateFixtures(team, print_only=False)
Thats the best I can do based on the code you pasted. I strongly suggest you rework some of it to be more readable.