Skip to content

Commit

Permalink
Feature/offer payment methods (#55)
Browse files Browse the repository at this point in the history
* Preliminary changes to payment methods

Preliminary changes to payment methods, includes model changes, multilanguage translation changes, admin view changes and a new api endpoint.

* Handle payment_method linking

* Fixed validation for Image publication rights.

This bug caused us problems with all new models requiring editable rights. This has now been resolved.

* Validate that the models have is_user_editable attribute

We also need to validate that the models have the is_user_editable function.

* Importer for 'Payment Methods' default values

Importer for Payment Methods that adds the default values into the database.

* Checks for the existence of both attributes.

Checks for the existence of both attributes.

* PMD update

PMD update

* Updated PMD index start value

Updated PMD index start value

* Update api.py

New offer update, changes to OfferSerializer

* Create 0083_auto_20211102_1005.py

New migration file for payment methods

* Update models.py

Removed unused import

Co-authored-by: ezkat <[email protected]>
Co-authored-by: Anthon98 <[email protected]>
  • Loading branch information
3 people authored Nov 3, 2021
1 parent d5d2f43 commit 7c29f24
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 17 deletions.
28 changes: 23 additions & 5 deletions events/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from reversion.admin import VersionAdmin
from admin_auto_filters.filters import AutocompleteFilter
from events.api import generate_id
from events.models import Place, License, DataSource, Event, Keyword, KeywordSet, Language
from events.models import Place, License, DataSource, Event, Keyword, KeywordSet, Language, PaymentMethod


class BaseAdmin(admin.ModelAdmin):
Expand Down Expand Up @@ -57,11 +57,14 @@ class EventAdmin(AutoIdBaseAdmin, TranslationAdmin, VersionAdmin):
'provider_contact_info', 'event_status', 'super_event', 'info_url', 'in_language',
'publication_status', 'replaced_by', 'deleted')
search_fields = ('name', 'location__name')
list_display = ('id', 'name', 'start_time', 'end_time', 'publisher', 'location')
list_filter = ('data_source', PublisherFilter, CreatedByFilter, LocationFilter)
list_display = ('id', 'name', 'start_time',
'end_time', 'publisher', 'location')
list_filter = ('data_source', PublisherFilter,
CreatedByFilter, LocationFilter)
ordering = ('-last_modified_time',)
date_hierarchy = 'end_time'
autocomplete_fields = ('location', 'keywords', 'audience', 'super_event', 'publisher', 'replaced_by')
autocomplete_fields = ('location', 'keywords', 'audience',
'super_event', 'publisher', 'replaced_by')

def get_readonly_fields(self, request, obj=None):
if obj:
Expand All @@ -78,7 +81,8 @@ class Media:

class KeywordAdmin(AutoIdBaseAdmin, TranslationAdmin, VersionAdmin):
# TODO: only allow user_editable editable fields
fields = ('id', 'data_source', 'origin_id', 'publisher', 'name', 'replaced_by', 'deprecated')
fields = ('id', 'data_source', 'origin_id', 'publisher',
'name', 'replaced_by', 'deprecated')
search_fields = ('name',)
list_display = ('id', 'name', 'n_events')
list_filter = ('data_source',)
Expand Down Expand Up @@ -194,3 +198,17 @@ def get_readonly_fields(self, request, obj=None):


admin.site.register(License, LicenseAdmin)


class PaymentAdmin(BaseAdmin, TranslationAdmin):
fields = ('id', 'name')
list_display = ('id', 'name')

def get_readonly_fields(self, request, obj=None):
if obj:
return ['id']
else:
return []


admin.site.register(PaymentMethod, PaymentAdmin)
52 changes: 42 additions & 10 deletions events/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
from events.extensions import apply_select_and_prefetch, get_extensions_from_request
from events.models import (
Place, Event, Keyword, KeywordSet, Language, OpeningHoursSpecification, EventLink,
Offer, DataSource, Image, PublicationStatus, PUBLICATION_STATUSES, License, Video
Offer, DataSource, Image, PublicationStatus, PUBLICATION_STATUSES, License, Video, PaymentMethod
)
from events.translation import EventTranslationOptions, ImageTranslationOptions
from helevents.models import User
Expand Down Expand Up @@ -260,6 +260,10 @@ def to_internal_value(self, value):
self.invalid_json_error % type(value).__name__)

url = value['@id']

if not isinstance(url, str):
url = str(url)

if not url:
if self.required:
raise serializers.ValidationError(_('This field is required.'))
Expand Down Expand Up @@ -513,12 +517,13 @@ def __init__(self, instance=None, files=None,
if not instance.data_source == self.data_source:
raise PermissionDenied()
else:
# without api key, the user will have to be admin
if not instance.is_user_editable() or not instance.can_be_edited_by(self.user):
# An exception to allow users to publish events using default images from the Imagebank
# even if they aren't bound to the Imagebank-organization for regular or admin rights.
if not isinstance(instance, Image):
raise PermissionDenied()
if not isinstance(instance, Image):
''' Without the API key, the user needs Admin rights.
An exception to allow users to publish events using default images from the Imagebank
even if they aren't bound to the Imagebank-organization for regular or admin rights. '''
if hasattr(instance, 'is_user_editable') and hasattr(instance, 'can_be_edited_by'):
if not instance.is_user_editable() or not instance.can_be_edited_by(self.user):
raise PermissionDenied()

def to_internal_value(self, data):
for field in self.system_generated_fields:
Expand Down Expand Up @@ -1134,7 +1139,29 @@ class Meta:
exclude = ['id', 'event']


class PaymentMethodSerializer(LinkedEventsSerializer):
view_name = 'paymentmethod-detail'
id = serializers.CharField(required=False)
name = serializers.CharField(required=False)

class Meta:
model = PaymentMethod
fields = '__all__'


class PaymentViewSet(JSONAPIViewMixin, viewsets.ReadOnlyModelViewSet):
queryset = PaymentMethod.objects.all()
serializer_class = PaymentMethodSerializer


register_view(PaymentViewSet, 'paymentmethod')


class OfferSerializer(TranslatedModelSerializer):
payment_methods = JSONLDRelatedField(
serializer=PaymentMethodSerializer, many=True, required=False, allow_empty=True, expanded=True,
view_name='paymentmethod-detail', queryset=PaymentMethod.objects.all())

class Meta:
model = Offer
exclude = ['id', 'event']
Expand Down Expand Up @@ -1467,9 +1494,11 @@ def create(self, validated_data):

event = super().create(validated_data)

# create and add related objects
for offer in offers:
Offer.objects.create(event=event, **offer)
payment_methods = offer.pop('payment_methods', [])
offer_instance = Offer.objects.create(event=event, **offer)
for payment_method in payment_methods:
offer_instance.payment_methods.add(payment_method)
for link in links:
EventLink.objects.create(event=event, **link)
for video in videos:
Expand Down Expand Up @@ -1539,7 +1568,10 @@ def update(self, instance, validated_data):
if isinstance(offers, list):
instance.offers.all().delete()
for offer in offers:
Offer.objects.create(event=instance, **offer)
payment_methods = offer.pop('payment_methods', [])
offer_instance = Offer.objects.create(event=instance, **offer)
for payment_method in payment_methods:
offer_instance.payment_methods.add(payment_method)

# update ext links
if isinstance(links, list):
Expand Down
71 changes: 71 additions & 0 deletions events/importer/payment_method_defaults.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-

# Dependencies.

# Logging:
import time
import logging
from os import mkdir
from os.path import abspath, join, dirname, exists, basename, splitext

# Django:
from django_orghierarchy.models import Organization
from django_orghierarchy.models import OrganizationClass
from events.models import BaseModel, PaymentMethod

# Importer specific:
from .base import Importer, register_importer

# Type checking:
from typing import Any

# Setup Logging:
if not exists(join(dirname(__file__), 'logs')):
mkdir(join(dirname(__file__), 'logs'))

logger = logging.getLogger(__name__) # Per module logger
curFileExt = basename(__file__)
curFile = splitext(curFileExt)[0]
logFile = \
logging.FileHandler(
'%s' % (join(dirname(__file__), 'logs', curFile+'.logs'))
)
logFile.setFormatter(
logging.Formatter(
'[%(asctime)s] <%(name)s> (%(lineno)d): %(message)s'
)
)
logFile.setLevel(logging.DEBUG)
logger.addHandler(
logFile
)


@register_importer
class PMDImporter(Importer):
# Required super 'base' class dependencies...
name = "payment_method_defaults" # Command calling name.
supported_languages = ['fi', 'sv', 'en'] # Language requirement.
data_source = None # Base data_source requirement.
organization = None # Base organization requirement.

def setup(self: 'events.importer.payment_method_defaults.PMDImporter') -> None:
data = [
'Käteinen',
'Maksukortti',
'Virikeseteli',
'Tyky-ranneke',
'Verkkomaksu',
'Mobile Pay',
'Museokortti',
'Lasku',
]

for idx, word in enumerate(data, start=1):
try:
pm = PaymentMethod()
pm.id = str(idx)
pm.name = word
pm.save()
except Exception as e:
logger.error(e)
33 changes: 33 additions & 0 deletions events/migrations/0083_auto_20211102_1005.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Generated by Django 2.2.24 on 2021-11-02 08:05

from django.db import migrations, models
import events.models


class Migration(migrations.Migration):

dependencies = [
('events', '0082_event_enrolment_url'),
]

operations = [
migrations.CreateModel(
name='PaymentMethod',
fields=[
('id', models.CharField(max_length=100, primary_key=True, serialize=False)),
('name', models.CharField(blank=True, max_length=25, verbose_name='Name')),
('name_fi', models.CharField(blank=True, max_length=25, null=True, verbose_name='Name')),
('name_sv', models.CharField(blank=True, max_length=25, null=True, verbose_name='Name')),
('name_en', models.CharField(blank=True, max_length=25, null=True, verbose_name='Name')),
('name_zh_hans', models.CharField(blank=True, max_length=25, null=True, verbose_name='Name')),
('name_ru', models.CharField(blank=True, max_length=25, null=True, verbose_name='Name')),
('name_ar', models.CharField(blank=True, max_length=25, null=True, verbose_name='Name')),
],
bases=(models.Model, events.models.SimpleValueMixin),
),
migrations.AddField(
model_name='offer',
name='payment_methods',
field=models.ManyToManyField(blank=True, related_name='paymentmethods', to='events.PaymentMethod'),
),
]
10 changes: 10 additions & 0 deletions events/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,14 @@ def keyword_added_or_removed(sender, model=None,
instance.save(update_fields=("n_events_changed",))


class PaymentMethod(models.Model, SimpleValueMixin):
id = models.CharField(max_length=100, primary_key=True)
name = models.CharField(verbose_name=('Name'), blank=True, max_length=25)

def value_fields(self):
return ['name']


class Offer(models.Model, SimpleValueMixin):
event = models.ForeignKey(
Event, on_delete=models.CASCADE, db_index=True, related_name='offers')
Expand All @@ -866,6 +874,8 @@ class Offer(models.Model, SimpleValueMixin):
# Don't expose is_free as an API field. It is used to distinguish
# between missing price info and confirmed free entry.
is_free = models.BooleanField(verbose_name=_('Is free'), default=False)
payment_methods = models.ManyToManyField(
PaymentMethod, blank=True, related_name='paymentmethods')

def value_fields(self):
return ['price', 'info_url', 'description', 'is_free']
Expand Down
13 changes: 11 additions & 2 deletions events/translation.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from modeltranslation.translator import translator, TranslationOptions
from .models import Language, Keyword, KeywordSet, Place, Event, Offer, License, Video, Image
from .models import Language, Keyword, KeywordSet, Place, Event, Offer, License, Video, Image, PaymentMethod


class LanguageTranslationOptions(TranslationOptions):
Expand All @@ -24,7 +24,8 @@ class KeywordSetTranslationOptions(TranslationOptions):


class PlaceTranslationOptions(TranslationOptions):
fields = ('name', 'description', 'info_url', 'street_address', 'address_locality', 'telephone')
fields = ('name', 'description', 'info_url',
'street_address', 'address_locality', 'telephone')


translator.register(Place, PlaceTranslationOptions)
Expand All @@ -39,6 +40,13 @@ class EventTranslationOptions(TranslationOptions):
translator.register(Event, EventTranslationOptions)


class PaymentMethodOptions(TranslationOptions):
fields = ('name',)


translator.register(PaymentMethod, PaymentMethodOptions)


class OfferTranslationOptions(TranslationOptions):
fields = ('price', 'info_url', 'description')

Expand All @@ -63,4 +71,5 @@ class VideoTranslationOptions(TranslationOptions):
class ImageTranslationOptions(TranslationOptions):
fields = ('alt_text', 'name')


translator.register(Image, ImageTranslationOptions)

0 comments on commit 7c29f24

Please sign in to comment.