From 4deb18ac3b2f76b504b5f62cfae3d05cdcbe62e3 Mon Sep 17 00:00:00 2001 From: Jonathan Sundqvist Date: Mon, 22 Oct 2018 18:43:00 +0200 Subject: [PATCH 01/29] Start of factories --- core/factory_apps/__init__.py | 0 core/factory_apps/booking.py | 0 core/factory_apps/location.py | 99 +++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 core/factory_apps/__init__.py create mode 100644 core/factory_apps/booking.py create mode 100644 core/factory_apps/location.py diff --git a/core/factory_apps/__init__.py b/core/factory_apps/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/core/factory_apps/booking.py b/core/factory_apps/booking.py new file mode 100644 index 00000000..e69de29b diff --git a/core/factory_apps/location.py b/core/factory_apps/location.py new file mode 100644 index 00000000..8761c41a --- /dev/null +++ b/core/factory_apps/location.py @@ -0,0 +1,99 @@ +from faker import Faker +from fakers.providers import lorem +from fakers.providers import profile +from fakers.providers import address +from faker.providers import BaseProvider +import factory +from core.models import Location +from core.models import Resource +from core.models import LocationFee +from core.models import Fee + +factory.Faker.add_provider(lorem) +factory.Faker.add_provider(profile) +factory.Faker.add_provider(address) + + +class Provider(BaseProvider): + # Note that the class name _must_ be ``Provider``. + def slug(self, provider): + fake = Faker() + value = getattr(fake, provider)() + return value.replace(' ', '-') + + +factory.Faker.add_provider(Provider) + + +class LocationFactory(factory.DjangoModelFactory): + class Meta: + model = Location + + name = factory.faker('street_name') + slug = factory.faker('slug', provider='street_name') + short_description = factory.faker('text') + address = factory.faker('street_address') + image = factory.django.ImageField(color='blue') + profile_image = factory.django.ImageField(color='red') + latitude = factory.faker('latitude') + longitude = factory.faker('longitude') + + welcome_email_days_ahead = factory.faker('random_int') + max_booking_days = factory.faker('random_int') + + stay_page = factory.faker('text') + front_page_stay = factory.faker('text') + front_page_participants = factory.faker('text') + announcement = factory.faker('text') + + house_access_code = factory.faker('word') + ssid = factory.faker('word') + ssid_password = factory.faker('word') + + timezone = factory.faker('word') + bank_account_number = factory.faker('random_int') + routing_number = factory.faker('random_int') + + bank_name = factory.faker('word') + name_on_account = factory.faker('word') + email_subject_prefix = factory.faker('word') + + check_out = factory.faker('word') + check_in = factory.faker('word') + visibility = factory.Iterator(['public', 'members', 'link']) + + + @factory.post_generation + def house_admins(self, create, extracted, **kwargs): + if not create: + # Simple build, do nothing. + return + + if extracted: + # A list of groups were passed in, use them + for user in extracted: + self.house_admins.add(user) + + @factory.post_generation + def readonly_admins(self, create, extracted, **kwargs): + if not create: + # Simple build, do nothing. + return + + if extracted: + # A list of groups were passed in, use them + for user in extracted: + self.readonly_admins.add(user) + + +class ResourceFactory(factory.DjangoModelFactory): + class Meta: + model = Resource + + name = factory.faker('name') + location = factory.SubFactory(LocationFactory) + default_rate = factory.faker('random_int') + description = factory.faker('text') + summary = factory.faker('sentence') + cancellation_policy = factory.faker('text') + image = factory.django.ImageField(color='green') From 67e09e847133e75fa1320d2d7d00daa83a5df745 Mon Sep 17 00:00:00 2001 From: Jonathan Sundqvist Date: Mon, 29 Oct 2018 22:26:20 +0100 Subject: [PATCH 02/29] Fix flake issues --- core/models.py | 68 ++++++++++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/core/models.py b/core/models.py index fb9c3b41..6307f364 100644 --- a/core/models.py +++ b/core/models.py @@ -3,20 +3,15 @@ from dateutil.relativedelta import relativedelta from django.contrib.auth.models import User -from django.core.files.storage import FileSystemStorage from django.db import models -from django.contrib.sites.models import Site -from django.core import urlresolvers from PIL import Image import os import datetime from django.conf import settings from django.core.files.storage import default_storage import uuid -import stripe -from django.db.models import Q, Sum +from django.db.models import Q from decimal import Decimal -from django.utils.safestring import mark_safe import calendar from django.utils import timezone from django.core.urlresolvers import reverse @@ -27,12 +22,7 @@ # imports for signals import django.dispatch from django.dispatch import receiver -from django.db.models.signals import pre_save, post_save, m2m_changed - -# mail imports -from django.core.mail import EmailMultiAlternatives -from django.template.loader import get_template -from django.template import Context +from django.db.models.signals import pre_save, m2m_changed # bank app imports from bank.models import Account, Transaction, Currency @@ -209,7 +199,6 @@ def has_capacity(self, arrive=None, depart=None): return False def events(self, user=None): - today = timezone.localtime(timezone.now()) if 'gather' in settings.INSTALLED_APPS: from gather.models import Event return Event.objects.upcoming(upto=5, current_user=user, location=self) @@ -357,10 +346,11 @@ def formatday(self, day, weekday): class ResourceManager(models.Manager): def backed_by(self, user): - resources = self.get_queryset().filter(backing__money_account__owners = user) + resources = self.get_queryset().filter(backing__money_account__owners=user) print(resources) return resources + class Resource(models.Model): name = models.CharField(max_length=200) location = models.ForeignKey(Location, related_name='resources', null=True) @@ -591,6 +581,7 @@ def set_next_backing(self, backers, new_backing_date): new_backing = Backing.objects.setup_new(resource=self, backers=backers, start=new_backing_date) logger.debug('created new backing %d' % new_backing.id) + class Fee(models.Model): description = models.CharField(max_length=100, verbose_name="Fee Name") percentage = models.FloatField(default=0, help_text="For example 5.2% = 0.052") @@ -1684,24 +1675,24 @@ def accounts_in_currency(self, currency): return list(self.user.accounts_owned.filter(currency=currency)) + list(self.user.accounts_administered.filter(currency=currency)) - User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u)[0]) User.rooms_backed = (lambda u: Resource.objects.backed_by(user=u)) User._meta.ordering = ['username'] + # Note: primary.accounts.'through' is django's name for the M2M class @receiver(m2m_changed, sender=UserProfile.primary_accounts.through) def primary_accounts_changed(sender, action, instance, reverse, pk_set, **kwargs): logger.debug('p2p_changed signal') logger.debug(action) if action == "pre_add": - logger.debug(instance) # should be a UserProfile unless reversed + logger.debug(instance) # should be a UserProfile unless reversed # since the sender is defined as UserProfile in the @receiver line, # UserProfile is the forward relation and Account is the reverse relation - if reverse: # Account.primary_for.add(...) + if reverse: # Account.primary_for.add(...) account = instance - else: # UserProfile.primary_accounts.add(...) + else: # UserProfile.primary_accounts.add(...) user_profile = instance for pk in pk_set: @@ -1718,6 +1709,7 @@ def primary_accounts_changed(sender, action, instance, reverse, pk_set, **kwargs # ensure user is an owner of primary account assert user_profile.user in account.owners.all() + @receiver(pre_save, sender=UserProfile) def size_images(sender, instance, **kwargs): try: @@ -1962,11 +1954,11 @@ def quantity_on(self, date, resource): def would_not_change_previous_quantity(self, capacity): previous_capacity = self._previous_capacity(capacity) - return (previous_capacity and - ( previous_capacity.quantity == capacity.quantity ) - and - (previous_capacity.accept_drft == capacity.accept_drft) - ) + return all([ + previous_capacity, + previous_capacity.quantity == capacity.quantity, + previous_capacity.accept_drft == capacity.accept_drft + ]) def same_as_next_quantity(self, capacity): logger.debug('same_as_next_quantity') @@ -1978,11 +1970,11 @@ def same_as_next_quantity(self, capacity): logger.debug(capacity.quantity) logger.debug(capacity.accept_drft) - return (next_capacity and - ( next_capacity.quantity == capacity.quantity) - and - ( next_capacity.accept_drft == capacity.accept_drft) - ) + return all([ + next_capacity, + next_capacity.quantity == capacity.quantity, + next_capacity.accept_drft == capacity.accept_drft + ]) class CapacityChange(models.Model): @@ -1996,9 +1988,10 @@ class CapacityChange(models.Model): class Meta: unique_together = ('start_date', 'resource',) + class BackingManager(models.Manager): def by_user(self, user): - return self.get_queryset().filter(money_account__owners = user) + return self.get_queryset().filter(money_account__owners=user) def setup_new(self, resource, backers, start): b = Backing(resource=resource, start=start) @@ -2012,6 +2005,7 @@ def setup_new(self, resource, backers, start): b.drft_account.save() return b + class Backing(models.Model): resource = models.ForeignKey(Resource, related_name='backings') money_account = models.ForeignKey(Account, related_name='+') @@ -2056,16 +2050,19 @@ def _setup_accounts(self, backers): assert not hasattr(self, 'money_account') and not hasattr(self, 'drft_account') # create accounts for this backing usd, _ = Currency.objects.get_or_create(name="USD", defaults={'symbol': '$'}) - ma = Account.objects.create(currency=usd, - name="%s Backing USD Account" % self.resource, - type = Account.CREDIT) + ma = Account.objects.create( + currency=usd, + name="%s Backing USD Account" % self.resource, + type=Account.CREDIT + ) ma.owners.add(*backers) self.money_account = ma drft, _ = Currency.objects.get_or_create(name='DRFT', defaults={'symbol': 'Ɖ'}) - da = Account.objects.create(currency=drft, - name="%s Backing DRFT Account" % self.resource, - type = Account.CREDIT) + da = Account.objects.create( + currency=drft, + name="%s Backing DRFT Account" % self.resource, + type=Account.CREDIT) da.owners.add(*backers) self.drft_account = da @@ -2074,6 +2071,7 @@ class HouseAccount(models.Model): location = models.ForeignKey(Location) account = models.ForeignKey(Account) + class UseTransaction(models.Model): use = models.ForeignKey(Use) transaction = models.ForeignKey(Transaction) From 31a31ee9ddd1e13f2a37d590b0b07304b73a3048 Mon Sep 17 00:00:00 2001 From: Jonathan Sundqvist Date: Mon, 29 Oct 2018 22:26:56 +0100 Subject: [PATCH 03/29] Add LocationFeeFactory --- core/factory_apps/__init__.py | 24 ++++++++++++++++++++++++ core/factory_apps/booking.py | 11 +++++++++++ core/factory_apps/location.py | 34 +++++++++++----------------------- 3 files changed, 46 insertions(+), 23 deletions(-) diff --git a/core/factory_apps/__init__.py b/core/factory_apps/__init__.py index e69de29b..d9b7e0a9 100644 --- a/core/factory_apps/__init__.py +++ b/core/factory_apps/__init__.py @@ -0,0 +1,24 @@ +from faker import Faker +from fakers.providers import lorem +from fakers.providers import profile +from fakers.providers import address +from fakers.providers import python +from faker.providers import BaseProvider +import factory + +factory.Faker.add_provider(python) +factory.Faker.add_provider(lorem) +factory.Faker.add_provider(profile) +factory.Faker.add_provider(address) + + +class Provider(BaseProvider): + # Note that the class name _must_ be ``Provider``. + def slug(self, provider): + fake = Faker() + value = getattr(fake, provider)() + return value.replace(' ', '-') + + +factory.Faker.add_provider(Provider) + diff --git a/core/factory_apps/booking.py b/core/factory_apps/booking.py index e69de29b..95b56049 100644 --- a/core/factory_apps/booking.py +++ b/core/factory_apps/booking.py @@ -0,0 +1,11 @@ +from . import factory +from core.models import Fee + + +class FeeFactory(factory.DjangoModelFactory): + class Meta: + model = Fee + + description = factory.faker('text') + percentage = factory.faker('pyfloat', left_digits=0, positive=True) + paid_by_house = factory.faker('pybool') diff --git a/core/factory_apps/location.py b/core/factory_apps/location.py index 8761c41a..668cdcb1 100644 --- a/core/factory_apps/location.py +++ b/core/factory_apps/location.py @@ -1,28 +1,8 @@ -from faker import Faker -from fakers.providers import lorem -from fakers.providers import profile -from fakers.providers import address -from faker.providers import BaseProvider -import factory +from . import factory from core.models import Location from core.models import Resource from core.models import LocationFee -from core.models import Fee - -factory.Faker.add_provider(lorem) -factory.Faker.add_provider(profile) -factory.Faker.add_provider(address) - - -class Provider(BaseProvider): - # Note that the class name _must_ be ``Provider``. - def slug(self, provider): - fake = Faker() - value = getattr(fake, provider)() - return value.replace(' ', '-') - - -factory.Faker.add_provider(Provider) +from .booking import FeeFactory class LocationFactory(factory.DjangoModelFactory): @@ -62,7 +42,6 @@ class Meta: check_in = factory.faker('word') visibility = factory.Iterator(['public', 'members', 'link']) - @factory.post_generation def house_admins(self, create, extracted, **kwargs): if not create: @@ -97,3 +76,12 @@ class Meta: summary = factory.faker('sentence') cancellation_policy = factory.faker('text') image = factory.django.ImageField(color='green') + + +class LocationFeeFactory(factory.DjangoModelFactory): + class Meta: + model = LocationFee + + location = factory.SubFactory(LocationFactory) + fee = factory.SubFactory(FeeFactory) + From 72494789ef744f42e29ca1eee969a3cc58ebd327 Mon Sep 17 00:00:00 2001 From: Jonathan Sundqvist Date: Mon, 29 Oct 2018 23:12:37 +0100 Subject: [PATCH 04/29] Outline of most factories needed --- core/factory_apps/__init__.py | 10 +++-- core/factory_apps/accounts.py | 9 +++++ core/factory_apps/booking.py | 11 ------ core/factory_apps/communication.py | 5 +++ core/factory_apps/events.py | 0 core/factory_apps/location.py | 63 +++++++++++++++++++++++++++++- core/factory_apps/payment.py | 52 ++++++++++++++++++++++++ core/factory_apps/user.py | 9 +++++ 8 files changed, 142 insertions(+), 17 deletions(-) create mode 100644 core/factory_apps/accounts.py delete mode 100644 core/factory_apps/booking.py create mode 100644 core/factory_apps/communication.py create mode 100644 core/factory_apps/events.py create mode 100644 core/factory_apps/payment.py create mode 100644 core/factory_apps/user.py diff --git a/core/factory_apps/__init__.py b/core/factory_apps/__init__.py index d9b7e0a9..d37d9e0e 100644 --- a/core/factory_apps/__init__.py +++ b/core/factory_apps/__init__.py @@ -1,11 +1,13 @@ from faker import Faker -from fakers.providers import lorem -from fakers.providers import profile -from fakers.providers import address -from fakers.providers import python +from faker.providers import lorem +from faker.providers import profile +from faker.providers import address +from faker.providers import python +from faker.providers import date_time from faker.providers import BaseProvider import factory +factory.Faker.add_provider(date_time) factory.Faker.add_provider(python) factory.Faker.add_provider(lorem) factory.Faker.add_provider(profile) diff --git a/core/factory_apps/accounts.py b/core/factory_apps/accounts.py new file mode 100644 index 00000000..b1c271e3 --- /dev/null +++ b/core/factory_apps/accounts.py @@ -0,0 +1,9 @@ +from . import factory + + +class HouseAccountFactory(factory.DjangoModelFactory): + pass + + +class UseTransactionFactory(factory.DjangoModelFactory): + pass diff --git a/core/factory_apps/booking.py b/core/factory_apps/booking.py deleted file mode 100644 index 95b56049..00000000 --- a/core/factory_apps/booking.py +++ /dev/null @@ -1,11 +0,0 @@ -from . import factory -from core.models import Fee - - -class FeeFactory(factory.DjangoModelFactory): - class Meta: - model = Fee - - description = factory.faker('text') - percentage = factory.faker('pyfloat', left_digits=0, positive=True) - paid_by_house = factory.faker('pybool') diff --git a/core/factory_apps/communication.py b/core/factory_apps/communication.py new file mode 100644 index 00000000..8e7aec1b --- /dev/null +++ b/core/factory_apps/communication.py @@ -0,0 +1,5 @@ +from . import factory + + +class EmailtemplateFactory(factory.DjangoModelFactory): + pass diff --git a/core/factory_apps/events.py b/core/factory_apps/events.py new file mode 100644 index 00000000..e69de29b diff --git a/core/factory_apps/location.py b/core/factory_apps/location.py index 668cdcb1..ac28a17c 100644 --- a/core/factory_apps/location.py +++ b/core/factory_apps/location.py @@ -1,8 +1,14 @@ -from . import factory +from django.contrib.flatpages.models import FlatPage + from core.models import Location from core.models import Resource from core.models import LocationFee +from core.models import LocationMenu +from core.models import LocationFlatPage +from core.models import LocationEmailTemplate +from core.models import CapacityChange from .booking import FeeFactory +from . import factory class LocationFactory(factory.DjangoModelFactory): @@ -71,7 +77,7 @@ class Meta: name = factory.faker('name') location = factory.SubFactory(LocationFactory) - default_rate = factory.faker('random_int') + default_rate = factory.faker('pydecimal', left_digits=0, positive=True) description = factory.faker('text') summary = factory.faker('sentence') cancellation_policy = factory.faker('text') @@ -85,3 +91,56 @@ class Meta: location = factory.SubFactory(LocationFactory) fee = factory.SubFactory(FeeFactory) + +class LocationMenuFactory(factory.DjangoModelFactory): + class Meta: + model = LocationMenu + + location = factory.SubFactory(LocationFactory) + name = factory.faker('text') + + +class FlatpageFactory(factory.DjangoModelFactory): + class Meta: + model = FlatPage + + +class LocationFlatPageFactory(factory.DjangoModelFactory): + class Meta: + model = LocationFlatPage + + menu = factory.SubFactory(LocationMenuFactory) + flatpage = factory.SubFactory(FlatpageFactory) + + +class LocationEmailTemplateFactory(factory.DjangoModelFactory): + class Meta: + model = LocationEmailTemplate + + location = factory.SubFactory(LocationFactory) + key = 'admin_daily_update' + text_body = factory.faker('text') + html_body = factory.faker('text') + + +class CapacityChangeFactory(factory.DjangoModelFactory): + class Meta: + model = CapacityChange + + created = factory.faker('past_datetime') + resource = factory.SubFactory(ResourceFactory) + start_date = factory.faker('future_date') + quantity = factory.faker('pyint') + accept_drft = factory.faker('pybool') + + +class LocationImageFactory(factory.DjangoModelFactory): + pass + + +class RoomImageFactory(factory.DjangoModelFactory): + pass + + +class BackingFactory(factory.DjangoModelFactory): + pass diff --git a/core/factory_apps/payment.py b/core/factory_apps/payment.py new file mode 100644 index 00000000..a546187e --- /dev/null +++ b/core/factory_apps/payment.py @@ -0,0 +1,52 @@ +from . import factory +from core.models import Fee + + +class FeeFactory(factory.DjangoModelFactory): + class Meta: + model = Fee + + description = factory.faker('text') + percentage = factory.faker('pyfloat', left_digits=0, positive=True) + paid_by_house = factory.faker('pybool') + + +class BillFactory(factory.DjangoModelFactory): + pass + + +class BillLineItem(factory.DjangoModelFactory): + pass + + +class SubscriptionFactory(factory.DjangoModelFactory): + pass + + +class SubscriptionBillFactory(factory.DjangoModelFactory): + pass + + +class BookingBillFactory(factory.DjangoModelFactory): + pass + + +class UseFactory(factory.DjangoModelFactory): + pass + + +class BookingFactory(factory.DjangoModelFactory): + pass + + +class PaymentFactory(factory.DjangoModelFactory): + pass + + +class UseNoteFactory(factory.DjangoModelFactory): + pass + + +class SubscriptionNoteFactory(factory.DjangoModelFactory): + pass + diff --git a/core/factory_apps/user.py b/core/factory_apps/user.py new file mode 100644 index 00000000..b7eb6bbe --- /dev/null +++ b/core/factory_apps/user.py @@ -0,0 +1,9 @@ +from . import factory + + +class UserProfileFactory(factory.DjangoModelFactory): + pass + + +class UserNote(factory.DjangoModelFactory): + pass From 1a0dbab7c7771bb001d89d7ba34ecc546aa3199d Mon Sep 17 00:00:00 2001 From: Jonathan Sundqvist Date: Mon, 29 Oct 2018 23:31:02 +0100 Subject: [PATCH 05/29] Fix flake errors in events models --- gather/models.py | 61 +++++++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/gather/models.py b/gather/models.py index e02c9ce2..21da1316 100755 --- a/gather/models.py +++ b/gather/models.py @@ -1,13 +1,12 @@ -import uuid, os, datetime +import uuid +import os import logging from django.conf import settings -from django.contrib.auth.models import User, Group +from django.contrib.auth.models import User from django.db import models from django.db.models.signals import post_save from django.utils import timezone -from PIL import Image -import requests from core.models import Location @@ -26,6 +25,7 @@ def __unicode__(self): class Meta: app_label = 'gather' + class EventSeries(models.Model): ''' Events may be associated with a series. A series has a name and desciption, and its own landing page which lists the associated events. ''' @@ -38,6 +38,7 @@ def __unicode__(self): class Meta: app_label = 'gather' + def event_img_upload_to(instance, filename): ext = filename.split('.')[-1] # rename file to random string @@ -49,16 +50,21 @@ def event_img_upload_to(instance, filename): os.makedirs(upload_abs_path) return os.path.join(upload_path, filename) + class EventManager(models.Manager): def upcoming(self, upto=None, current_user=None, location=None): # return the events happening today or in the future, returning up to # the number of events specified in the 'upto' argument. today = timezone.now() logger.debug(today) + qs = super().get_queryset() + upcoming = qs.filter(end__gte=today).exclude( + status=Event.CANCELED + ).order_by('start') - upcoming = super(EventManager, self).get_queryset().filter(end__gte = today).exclude(status=Event.CANCELED).order_by('start') if location: upcoming = upcoming.filter(location=location) + viewable_upcoming = [] for event in upcoming: if event.is_viewable(current_user): @@ -71,7 +77,7 @@ def upcoming(self, upto=None, current_user=None, location=None): class Meta: app_label = 'gather' -# Create your models here. + class Event(models.Model): PENDING = 'waiting for approval' FEEDBACK = 'seeking feedback' @@ -106,15 +112,15 @@ class Event(models.Model): description = models.TextField(help_text="Basic HTML markup is supported for your event description.") image = models.ImageField(upload_to=event_img_upload_to) attendees = models.ManyToManyField(User, related_name="events_attending", blank=True) - notifications = models.BooleanField(default = True) + notifications = models.BooleanField(default=True) # where, site, place, venue - where = models.CharField(verbose_name = 'Where will the event be held?', max_length=500, help_text="Either a specific room at this location or an address if elsewhere") + where = models.CharField(verbose_name='Where will the event be held?', max_length=500, help_text="Either a specific room at this location or an address if elsewhere") creator = models.ForeignKey(User, related_name="events_created") organizers = models.ManyToManyField(User, related_name="events_organized", blank=True) organizer_notes = models.TextField(blank=True, null=True, help_text="These will only be visible to other organizers") limit = models.IntegerField(default=0, help_text="Specify a cap on the number of RSVPs, or 0 for no limit.", blank=True) - visibility = models.CharField(choices = event_visibility, max_length=200, default=PUBLIC, help_text="Community events are visible only to community members. Private events are visible to those who have the link.") - status = models.CharField(choices = event_statuses, default=PENDING, max_length=200, verbose_name='Review Status', blank=True) + visibility = models.CharField(choices=event_visibility, max_length=200, default=PUBLIC, help_text="Community events are visible only to community members. Private events are visible to those who have the link.") # noqa + status = models.CharField(choices=event_statuses, default=PENDING, max_length=200, verbose_name='Review Status', blank=True) endorsements = models.ManyToManyField(User, related_name="events_endorsed", blank=True) # the location field is optional but lets you associate an event with a # specific location object that is also managed by this software. a single @@ -149,37 +155,38 @@ def is_viewable(self, current_user): is_community_member = False # ok now let's see... - if ( - (self.status == 'live' and self.visibility == Event.PUBLIC) - or - ( - is_event_admin - or - current_user == self.creator - or - current_user in self.organizers.all() - or - current_user in self.attendees.all() - ) - or - (is_community_member and self.visibility != Event.PRIVATE) - ): + can_view = any([ + self.status == 'live' and self.visibility == Event.PUBLIC, + is_event_admin, + current_user == self.creator, + current_user in self.organizers.all(), + current_user in self.attendees.all(), + is_community_member and self.visibility != Event.PRIVATE + + ]) + + if can_view: viewable = True else: viewable = False return viewable + def default_event_status(sender, instance, created, using, **kwargs): logger.debug(instance) logger.debug(created) logger.debug(instance.status) - if created == True: + + if created: if instance.creator in instance.admin.users.all(): instance.status = Event.FEEDBACK else: instance.status = Event.PENDING + + post_save.connect(default_event_status, sender=Event) + class EventNotifications(models.Model): user = models.OneToOneField(User, related_name='event_notifications') # send reminders on day-of the event? @@ -191,8 +198,10 @@ class EventNotifications(models.Model): class Meta: app_label = 'gather' + User.event_notifications = property(lambda u: EventNotifications.objects.get_or_create(user=u)[0]) + # override the save method of the User model to create the EventNotifications # object automatically for new users def add_user_event_notifications(sender, instance, created, using, **kwargs): From 52a8fc8d52296240c969dfdeeac4bc96f7c25166 Mon Sep 17 00:00:00 2001 From: Jonathan Sundqvist Date: Tue, 30 Oct 2018 18:50:46 +0100 Subject: [PATCH 06/29] Factories for events --- core/factory_apps/events.py | 119 ++++++++++++++++++++++++++++++++++++ core/factory_apps/user.py | 17 ++++++ 2 files changed, 136 insertions(+) diff --git a/core/factory_apps/events.py b/core/factory_apps/events.py index e69de29b..3a73c03b 100644 --- a/core/factory_apps/events.py +++ b/core/factory_apps/events.py @@ -0,0 +1,119 @@ +from . import factory +from .location import LocationFactory + +from gather.models import EventAdminGroup +from gather.models import EventSeries +from gather.models import Event +from gather.models import EventNotifications + +from core.factory_apps.user import UserFactory + + +class EventAdminGroupFactory(factory.DjangoModelFactory): + class Meta: + model = EventAdminGroup + + location = factory.SubFactory(LocationFactory) + + @factory.post_generation + def users(self, create, extracted, **kwargs): + if not create: + # Simple build, do nothing. + return + + if extracted: + # A list of groups were passed in, use them + for user in extracted: + self.users.add(user) + + +class EventSeriesFactory(factory.DjangoModelFactory): + class Meta: + model = EventSeries + + name = factory.faker('word') + description = factory.faker('paragraph') + + +class EventFactory(factory.DjangoModelFactory): + class Meta: + model = Event + + created = factory.faker('past_datetime') + updated = factory.faker('past_datetime') + start = factory.faker('future_datetime') + end = factory.faker('future_datetime') + + title = factory.faker('words') + slug = factory.faker('words') + + description = factory.faker('paragraph') + image = factory.django.ImageField(color='gray') + + notifications = factory.faker('pyboolean') + + where = factory.faker('city') + creator = factory.SubFactory(UserFactory) + + organizer_notes = factory.faker('paragraph') + + limit = factory.faker('random_digit_or_empty') + visibility = Event.PUBLIC + status = Event.PENDING + + location = factory.SubFactory(LocationFactory) + series = factory.SubFactory(EventSeriesFactory) + admin = factory.SubFactory(EventAdminGroupFactory) + + @factory.post_generation + def attendees(self, create, extracted, **kwargs): + if not create: + return + + if extracted: + for users in extracted: + self.attendees.add(users) + + @factory.post_generation + def organizers(self, create, extracted, **kwargs): + if not create: + return + + if extracted: + for users in extracted: + self.organizers.add(users) + + @factory.post_generation + def endorsements(self, create, extracted, **kwargs): + if not create: + return + + if extracted: + for users in extracted: + self.endorsements.add(users) + + +class EventNotificationFactory(factory.DjangoModelFactory): + class Meta: + model = EventNotifications + + user = factory.SubFactory(UserFactory) + reminders = factory.faker('pyboolean') + + @factory.post_generation + def location_weekly(self, create, extracted, **kwargs): + if not create: + return + + if extracted: + for location in extracted: + self.location_weekly.add(location) + + @factory.post_generation + def location_publish(self, create, extracted, **kwargs): + if not create: + return + + if extracted: + for location in extracted: + self.location_publish.add(location) diff --git a/core/factory_apps/user.py b/core/factory_apps/user.py index b7eb6bbe..a61160c4 100644 --- a/core/factory_apps/user.py +++ b/core/factory_apps/user.py @@ -1,5 +1,22 @@ +from django.contrib.auth import get_user_model + from . import factory +User = get_user_model() + + +class UserFactory(factory.DjangoModelFactory): + class Meta: + model = User + + username = factory.faker('name') + first_name = factory.faker('name') + last_name = factory.faker('lastname') + email = factory.faker('email') + is_staff = factory.faker('pyboolean') + is_active = True + is_superuser = False + class UserProfileFactory(factory.DjangoModelFactory): pass From b28d490ba1bc06293e7de987b2dfd28cfb40d01f Mon Sep 17 00:00:00 2001 From: Jonathan Sundqvist Date: Tue, 30 Oct 2018 19:38:51 +0100 Subject: [PATCH 07/29] Fix bug with imports --- core/factory_apps/events.py | 2 +- core/views/booking.py | 3 ++- core/views/use.py | 3 ++- gather/models.py | 1 - 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/core/factory_apps/events.py b/core/factory_apps/events.py index 3a73c03b..b8a9bc9b 100644 --- a/core/factory_apps/events.py +++ b/core/factory_apps/events.py @@ -6,7 +6,7 @@ from gather.models import Event from gather.models import EventNotifications -from core.factory_apps.user import UserFactory +from .user import UserFactory class EventAdminGroupFactory(factory.DjangoModelFactory): diff --git a/core/views/booking.py b/core/views/booking.py index 801acb2e..68d8833b 100644 --- a/core/views/booking.py +++ b/core/views/booking.py @@ -3,6 +3,7 @@ from django.conf import settings from django.contrib import messages +from django.contrib.sites.models import Site from django.contrib.auth.decorators import login_required from django.core.urlresolvers import reverse from django.http import HttpResponse, HttpResponseRedirect @@ -20,7 +21,7 @@ from .view_helpers import _get_user_and_perms from core.emails import send_booking_receipt, new_booking_notify from core.forms import BookingUseForm -from core.models import Booking, Use, Location, Site +from core.models import Booking, Use, Location logger = logging.getLogger(__name__) diff --git a/core/views/use.py b/core/views/use.py index d73d05e3..6c17efde 100644 --- a/core/views/use.py +++ b/core/views/use.py @@ -5,10 +5,11 @@ from django.shortcuts import get_object_or_404 import logging from django.contrib import messages +from django.contrib.sites.models import Site from django.http import HttpResponse, HttpResponseRedirect from django.core.urlresolvers import reverse -from core.models import Use, Location, Site +from core.models import Use, Location @login_required def UseDetail(request, use_id, location_slug): diff --git a/gather/models.py b/gather/models.py index 21da1316..c868cad7 100755 --- a/gather/models.py +++ b/gather/models.py @@ -209,4 +209,3 @@ def add_user_event_notifications(sender, instance, created, using, **kwargs): # defined with get_or_create, above. instance.event_notifications return - From 17c59c97059014d5323e51edd76bb661f4f34118 Mon Sep 17 00:00:00 2001 From: Jonathan Sundqvist Date: Tue, 30 Oct 2018 20:17:33 +0100 Subject: [PATCH 08/29] Start of payment factories --- core/factory_apps/payment.py | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/core/factory_apps/payment.py b/core/factory_apps/payment.py index a546187e..10d31722 100644 --- a/core/factory_apps/payment.py +++ b/core/factory_apps/payment.py @@ -1,5 +1,6 @@ from . import factory from core.models import Fee +from core import models class FeeFactory(factory.DjangoModelFactory): @@ -12,41 +13,51 @@ class Meta: class BillFactory(factory.DjangoModelFactory): - pass + class Meta: + model = models.Bill class BillLineItem(factory.DjangoModelFactory): - pass + class Meta: + model = models.BillLineItem class SubscriptionFactory(factory.DjangoModelFactory): - pass + class Meta: + model = models.Subscription class SubscriptionBillFactory(factory.DjangoModelFactory): - pass + class Meta: + model = models.SubscriptionBill class BookingBillFactory(factory.DjangoModelFactory): - pass + class Meta: + model = models.BookingBill class UseFactory(factory.DjangoModelFactory): - pass + class Meta: + model = models.Use class BookingFactory(factory.DjangoModelFactory): - pass + class Meta: + model = models.Booking class PaymentFactory(factory.DjangoModelFactory): - pass + class Meta: + model = models.Payment class UseNoteFactory(factory.DjangoModelFactory): - pass + class Meta: + model = models.UseNote class SubscriptionNoteFactory(factory.DjangoModelFactory): - pass + class Meta: + model = models.SubscriptionNote From e17d5bfcdc432ab43eca0958298596bf67ee7beb Mon Sep 17 00:00:00 2001 From: Jonathan Sundqvist Date: Tue, 30 Oct 2018 20:26:22 +0100 Subject: [PATCH 09/29] Communications factory --- core/factory_apps/communication.py | 12 +++++++++++- core/models.py | 6 ++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/core/factory_apps/communication.py b/core/factory_apps/communication.py index 8e7aec1b..f2959767 100644 --- a/core/factory_apps/communication.py +++ b/core/factory_apps/communication.py @@ -1,5 +1,15 @@ from . import factory +from core import models +from .user import UserFactory class EmailtemplateFactory(factory.DjangoModelFactory): - pass + class Meta: + model = models.EmailTemplate + + body = factory.faker('paragraph') + subject = factory.faker('words') + name = factory.faker('words') + creator = factory.SubFactory(UserFactory) + shared = factory.faker('pyboolean') + context = models.EmailTemplate.BOOKING diff --git a/core/models.py b/core/models.py index 6307f364..6cba7b8f 100644 --- a/core/models.py +++ b/core/models.py @@ -1769,9 +1769,11 @@ class EmailTemplate(models.Model): SUBJECT_PREFIX = settings.EMAIL_SUBJECT_PREFIX FROM_ADDRESS = settings.DEFAULT_FROM_EMAIL + BOOKING = 'booking' + SUBSCRIPTION = 'subscription' context_options = ( - ('booking', 'Booking'), - ('subscription', 'Subscription') + (BOOKING, 'Booking'), + (SUBSCRIPTION, 'Subscription') ) body = models.TextField(verbose_name="The body of the email") From 6f5d23de7737c440dea3b36358fe3f7dc8fa5ccf Mon Sep 17 00:00:00 2001 From: Jonathan Sundqvist Date: Tue, 30 Oct 2018 21:40:05 +0100 Subject: [PATCH 10/29] Capitalize Faker --- core/factory_apps/communication.py | 8 ++-- core/factory_apps/events.py | 28 ++++++------ core/factory_apps/location.py | 70 +++++++++++++++--------------- core/factory_apps/user.py | 10 ++--- 4 files changed, 58 insertions(+), 58 deletions(-) diff --git a/core/factory_apps/communication.py b/core/factory_apps/communication.py index f2959767..ce97d9da 100644 --- a/core/factory_apps/communication.py +++ b/core/factory_apps/communication.py @@ -7,9 +7,9 @@ class EmailtemplateFactory(factory.DjangoModelFactory): class Meta: model = models.EmailTemplate - body = factory.faker('paragraph') - subject = factory.faker('words') - name = factory.faker('words') + body = factory.Faker('paragraph') + subject = factory.Faker('words') + name = factory.Faker('words') creator = factory.SubFactory(UserFactory) - shared = factory.faker('pyboolean') + shared = factory.Faker('pyboolean') context = models.EmailTemplate.BOOKING diff --git a/core/factory_apps/events.py b/core/factory_apps/events.py index b8a9bc9b..6cfe1935 100644 --- a/core/factory_apps/events.py +++ b/core/factory_apps/events.py @@ -31,33 +31,33 @@ class EventSeriesFactory(factory.DjangoModelFactory): class Meta: model = EventSeries - name = factory.faker('word') - description = factory.faker('paragraph') + name = factory.Faker('word') + description = factory.Faker('paragraph') class EventFactory(factory.DjangoModelFactory): class Meta: model = Event - created = factory.faker('past_datetime') - updated = factory.faker('past_datetime') - start = factory.faker('future_datetime') - end = factory.faker('future_datetime') + created = factory.Faker('past_datetime') + updated = factory.Faker('past_datetime') + start = factory.Faker('future_datetime') + end = factory.Faker('future_datetime') - title = factory.faker('words') - slug = factory.faker('words') + title = factory.Faker('words') + slug = factory.Faker('words') - description = factory.faker('paragraph') + description = factory.Faker('paragraph') image = factory.django.ImageField(color='gray') - notifications = factory.faker('pyboolean') + notifications = factory.Faker('pyboolean') - where = factory.faker('city') + where = factory.Faker('city') creator = factory.SubFactory(UserFactory) - organizer_notes = factory.faker('paragraph') + organizer_notes = factory.Faker('paragraph') - limit = factory.faker('random_digit_or_empty') + limit = factory.Faker('random_digit_or_empty') visibility = Event.PUBLIC status = Event.PENDING @@ -98,7 +98,7 @@ class Meta: model = EventNotifications user = factory.SubFactory(UserFactory) - reminders = factory.faker('pyboolean') + reminders = factory.Faker('pyboolean') @factory.post_generation def location_weekly(self, create, extracted, **kwargs): diff --git a/core/factory_apps/location.py b/core/factory_apps/location.py index ac28a17c..e090406e 100644 --- a/core/factory_apps/location.py +++ b/core/factory_apps/location.py @@ -15,37 +15,37 @@ class LocationFactory(factory.DjangoModelFactory): class Meta: model = Location - name = factory.faker('street_name') - slug = factory.faker('slug', provider='street_name') - short_description = factory.faker('text') - address = factory.faker('street_address') + name = factory.Faker('street_name') + slug = factory.Faker('slug', provider='street_name') + short_description = factory.Faker('text') + address = factory.Faker('street_address') image = factory.django.ImageField(color='blue') profile_image = factory.django.ImageField(color='red') - latitude = factory.faker('latitude') - longitude = factory.faker('longitude') + latitude = factory.Faker('latitude') + longitude = factory.Faker('longitude') - welcome_email_days_ahead = factory.faker('random_int') - max_booking_days = factory.faker('random_int') + welcome_email_days_ahead = factory.Faker('random_int') + max_booking_days = factory.Faker('random_int') - stay_page = factory.faker('text') - front_page_stay = factory.faker('text') - front_page_participants = factory.faker('text') - announcement = factory.faker('text') + stay_page = factory.Faker('text') + front_page_stay = factory.Faker('text') + front_page_participants = factory.Faker('text') + announcement = factory.Faker('text') - house_access_code = factory.faker('word') - ssid = factory.faker('word') - ssid_password = factory.faker('word') + house_access_code = factory.Faker('word') + ssid = factory.Faker('word') + ssid_password = factory.Faker('word') - timezone = factory.faker('word') - bank_account_number = factory.faker('random_int') - routing_number = factory.faker('random_int') + timezone = factory.Faker('word') + bank_account_number = factory.Faker('random_int') + routing_number = factory.Faker('random_int') - bank_name = factory.faker('word') - name_on_account = factory.faker('word') - email_subject_prefix = factory.faker('word') + bank_name = factory.Faker('word') + name_on_account = factory.Faker('word') + email_subject_prefix = factory.Faker('word') - check_out = factory.faker('word') - check_in = factory.faker('word') + check_out = factory.Faker('word') + check_in = factory.Faker('word') visibility = factory.Iterator(['public', 'members', 'link']) @factory.post_generation @@ -75,12 +75,12 @@ class ResourceFactory(factory.DjangoModelFactory): class Meta: model = Resource - name = factory.faker('name') + name = factory.Faker('name') location = factory.SubFactory(LocationFactory) - default_rate = factory.faker('pydecimal', left_digits=0, positive=True) - description = factory.faker('text') - summary = factory.faker('sentence') - cancellation_policy = factory.faker('text') + default_rate = factory.Faker('pydecimal', left_digits=0, positive=True) + description = factory.Faker('text') + summary = factory.Faker('sentence') + cancellation_policy = factory.Faker('text') image = factory.django.ImageField(color='green') @@ -97,7 +97,7 @@ class Meta: model = LocationMenu location = factory.SubFactory(LocationFactory) - name = factory.faker('text') + name = factory.Faker('text') class FlatpageFactory(factory.DjangoModelFactory): @@ -119,19 +119,19 @@ class Meta: location = factory.SubFactory(LocationFactory) key = 'admin_daily_update' - text_body = factory.faker('text') - html_body = factory.faker('text') + text_body = factory.Faker('text') + html_body = factory.Faker('text') class CapacityChangeFactory(factory.DjangoModelFactory): class Meta: model = CapacityChange - created = factory.faker('past_datetime') + created = factory.Faker('past_datetime') resource = factory.SubFactory(ResourceFactory) - start_date = factory.faker('future_date') - quantity = factory.faker('pyint') - accept_drft = factory.faker('pybool') + start_date = factory.Faker('future_date') + quantity = factory.Faker('pyint') + accept_drft = factory.Faker('pybool') class LocationImageFactory(factory.DjangoModelFactory): diff --git a/core/factory_apps/user.py b/core/factory_apps/user.py index a61160c4..169601ef 100644 --- a/core/factory_apps/user.py +++ b/core/factory_apps/user.py @@ -9,11 +9,11 @@ class UserFactory(factory.DjangoModelFactory): class Meta: model = User - username = factory.faker('name') - first_name = factory.faker('name') - last_name = factory.faker('lastname') - email = factory.faker('email') - is_staff = factory.faker('pyboolean') + username = factory.Faker('name') + first_name = factory.Faker('name') + last_name = factory.Faker('lastname') + email = factory.Faker('email') + is_staff = factory.Faker('pyboolean') is_active = True is_superuser = False From ca3852c919a111cd17f5954c5232e414720c1252 Mon Sep 17 00:00:00 2001 From: Jonathan Sundqvist Date: Tue, 30 Oct 2018 21:48:33 +0100 Subject: [PATCH 11/29] Payment factories --- core/factory_apps/__init__.py | 2 + core/factory_apps/payment.py | 110 ++++++++++++++++++++++++++++++---- 2 files changed, 99 insertions(+), 13 deletions(-) diff --git a/core/factory_apps/__init__.py b/core/factory_apps/__init__.py index d37d9e0e..1eb29236 100644 --- a/core/factory_apps/__init__.py +++ b/core/factory_apps/__init__.py @@ -4,9 +4,11 @@ from faker.providers import address from faker.providers import python from faker.providers import date_time +from faker.providers import misc from faker.providers import BaseProvider import factory +factory.Faker.add_provider(misc) factory.Faker.add_provider(date_time) factory.Faker.add_provider(python) factory.Faker.add_provider(lorem) diff --git a/core/factory_apps/payment.py b/core/factory_apps/payment.py index 10d31722..f742e3eb 100644 --- a/core/factory_apps/payment.py +++ b/core/factory_apps/payment.py @@ -2,55 +2,140 @@ from core.models import Fee from core import models +from .user import UserFactory +from .location import LocationFactory +from .location import ResourceFactory -class FeeFactory(factory.DjangoModelFactory): + +class SubscriptionFactory(factory.DjangoModelFactory): class Meta: - model = Fee + model = models.Subscription - description = factory.faker('text') - percentage = factory.faker('pyfloat', left_digits=0, positive=True) - paid_by_house = factory.faker('pybool') + created = factory.Faker('past_datetime') + updated = factory.Faker('past_datetime') + created_by = factory.SubFactory(UserFactory) + location = factory.SubFactory(LocationFactory) + user = factory.SubFactory(UserFactory) -class BillFactory(factory.DjangoModelFactory): - class Meta: - model = models.Bill + price = factory.Faker('pydecimal') + description = factory.Faker('words') + start_date = factory.Faker('future_date') + end_date = factory.Faker('future_date') -class BillLineItem(factory.DjangoModelFactory): + +class FeeFactory(factory.DjangoModelFactory): class Meta: - model = models.BillLineItem + model = Fee + description = factory.Faker('text') + percentage = factory.Faker('pyfloat', left_digits=0, positive=True) + paid_by_house = factory.Faker('pybool') -class SubscriptionFactory(factory.DjangoModelFactory): + +class BillFactory(factory.DjangoModelFactory): + '''Bookings, BillLineItem or Subscription''' class Meta: - model = models.Subscription + model = models.Bill + + generated_on = factory.Faker('past_datetime') + comment = factory.Faker('paragraph') class SubscriptionBillFactory(factory.DjangoModelFactory): class Meta: model = models.SubscriptionBill + generated_on = factory.Faker('past_datetime') + comment = factory.Faker('paragraph') + + period_start = factory.Faker('future_date') + period_end = factory.Faker('future_date') + subscription = factory.SubFactory(SubscriptionFactory) + class BookingBillFactory(factory.DjangoModelFactory): class Meta: model = models.BookingBill + generated_on = factory.Faker('past_datetime') + comment = factory.Faker('paragraph') + + +class BillLineItem(factory.DjangoModelFactory): + class Meta: + model = models.BillLineItem + + bill = factory.SubFactory(BillFactory) + fee = factory.SubFactory(FeeFactory) + + description = factory.Faker('words') + amount = factory.Faker('pydecimal') + paid_by_house = factory.Faker('pyboolean') + custom = factory.Faker('pyboolean') + class UseFactory(factory.DjangoModelFactory): class Meta: model = models.Use + created = factory.Faker('past_datetime') + updated = factory.Faker('past_datetime') + + location = factory.SubFactory(LocationFactory) + user = factory.SubFactory(UserFactory) + resource = factory.SubFactory(ResourceFactory) + + status = models.Use.PENDING + arrive = factory.Faker('future_date') + depart = factory.Faker('future_date') + arrival_time = factory.Faker('words') + purpose = factory.Faker('paragraph') + last_msg = factory.Faker('past_datetime') + accounted_by = models.Use.FIAT + class BookingFactory(factory.DjangoModelFactory): + # deprecated fields not modeled in factory class Meta: model = models.Booking + created = factory.Faker('past_datetime') + updated = factory.Faker('past_datetime') + + comments = factory.Faker('paragraph') + rate = factory.Faker('pydecimal') + uuid = factory.Faker('uuid4') + + bill = factory.SubFactory(BookingBillFactory) + use = factory.SubFactory(UseFactory) + + @factory.post_generation + def suppressed_fees(self, create, extracted, **kwargs): + if not create: + # Simple build, do nothing. + return + + if extracted: + # A list of groups were passed in, use them + for fee in extracted: + self.suppressed_fees.add(fee) + class PaymentFactory(factory.DjangoModelFactory): class Meta: model = models.Payment + bill = factory.SubFactory(BillFactory) + user = factory.SubFactory(UserFactory) + payment_date = factory.Faker('past_datetime') + + # payment_service and payment_method may be empty so ignoring those + paid_amount = factory.Faker('pydecimal') + transaction_id = factory.Faker('uuid4') + last4 = factory.Faker('pyint') + class UseNoteFactory(factory.DjangoModelFactory): class Meta: @@ -60,4 +145,3 @@ class Meta: class SubscriptionNoteFactory(factory.DjangoModelFactory): class Meta: model = models.SubscriptionNote - From 9c3e75d551e7421eb5dc587c344bcd20ec090bc9 Mon Sep 17 00:00:00 2001 From: Jonathan Sundqvist Date: Tue, 30 Oct 2018 23:53:43 +0100 Subject: [PATCH 12/29] Fix errors when generating data --- core/factory_apps/__init__.py | 4 ++-- core/factory_apps/communication.py | 2 +- core/factory_apps/events.py | 4 ++-- core/factory_apps/location.py | 16 +++++++++++++--- core/factory_apps/payment.py | 23 +++++++---------------- core/factory_apps/user.py | 6 +++--- 6 files changed, 28 insertions(+), 27 deletions(-) diff --git a/core/factory_apps/__init__.py b/core/factory_apps/__init__.py index 1eb29236..cd065649 100644 --- a/core/factory_apps/__init__.py +++ b/core/factory_apps/__init__.py @@ -18,9 +18,9 @@ class Provider(BaseProvider): # Note that the class name _must_ be ``Provider``. - def slug(self, provider): + def slug(self, name): fake = Faker() - value = getattr(fake, provider)() + value = getattr(fake, name)() return value.replace(' ', '-') diff --git a/core/factory_apps/communication.py b/core/factory_apps/communication.py index ce97d9da..87fa2438 100644 --- a/core/factory_apps/communication.py +++ b/core/factory_apps/communication.py @@ -11,5 +11,5 @@ class Meta: subject = factory.Faker('words') name = factory.Faker('words') creator = factory.SubFactory(UserFactory) - shared = factory.Faker('pyboolean') + shared = factory.Faker('pybool') context = models.EmailTemplate.BOOKING diff --git a/core/factory_apps/events.py b/core/factory_apps/events.py index 6cfe1935..22879203 100644 --- a/core/factory_apps/events.py +++ b/core/factory_apps/events.py @@ -50,7 +50,7 @@ class Meta: description = factory.Faker('paragraph') image = factory.django.ImageField(color='gray') - notifications = factory.Faker('pyboolean') + notifications = factory.Faker('pybool') where = factory.Faker('city') creator = factory.SubFactory(UserFactory) @@ -98,7 +98,7 @@ class Meta: model = EventNotifications user = factory.SubFactory(UserFactory) - reminders = factory.Faker('pyboolean') + reminders = factory.Faker('pybool') @factory.post_generation def location_weekly(self, create, extracted, **kwargs): diff --git a/core/factory_apps/location.py b/core/factory_apps/location.py index e090406e..24115cb8 100644 --- a/core/factory_apps/location.py +++ b/core/factory_apps/location.py @@ -7,16 +7,26 @@ from core.models import LocationFlatPage from core.models import LocationEmailTemplate from core.models import CapacityChange -from .booking import FeeFactory +from core.models import Fee from . import factory +class FeeFactory(factory.DjangoModelFactory): + class Meta: + model = Fee + + description = factory.Faker('text') + percentage = factory.Faker('pyfloat', left_digits=0, positive=True) + paid_by_house = factory.Faker('pybool') + + class LocationFactory(factory.DjangoModelFactory): class Meta: model = Location + django_get_or_create = ('slug',) name = factory.Faker('street_name') - slug = factory.Faker('slug', provider='street_name') + slug = factory.Faker('slug', name='street_name') short_description = factory.Faker('text') address = factory.Faker('street_address') image = factory.django.ImageField(color='blue') @@ -29,7 +39,7 @@ class Meta: stay_page = factory.Faker('text') front_page_stay = factory.Faker('text') - front_page_participants = factory.Faker('text') + front_page_participate = factory.Faker('text') announcement = factory.Faker('text') house_access_code = factory.Faker('word') diff --git a/core/factory_apps/payment.py b/core/factory_apps/payment.py index f742e3eb..b28b9715 100644 --- a/core/factory_apps/payment.py +++ b/core/factory_apps/payment.py @@ -1,10 +1,10 @@ from . import factory -from core.models import Fee from core import models from .user import UserFactory from .location import LocationFactory from .location import ResourceFactory +from .location import FeeFactory class SubscriptionFactory(factory.DjangoModelFactory): @@ -18,22 +18,13 @@ class Meta: location = factory.SubFactory(LocationFactory) user = factory.SubFactory(UserFactory) - price = factory.Faker('pydecimal') + price = factory.Faker('pydecimal', left_digits=3, positive=True) description = factory.Faker('words') start_date = factory.Faker('future_date') end_date = factory.Faker('future_date') -class FeeFactory(factory.DjangoModelFactory): - class Meta: - model = Fee - - description = factory.Faker('text') - percentage = factory.Faker('pyfloat', left_digits=0, positive=True) - paid_by_house = factory.Faker('pybool') - - class BillFactory(factory.DjangoModelFactory): '''Bookings, BillLineItem or Subscription''' class Meta: @@ -71,9 +62,9 @@ class Meta: fee = factory.SubFactory(FeeFactory) description = factory.Faker('words') - amount = factory.Faker('pydecimal') - paid_by_house = factory.Faker('pyboolean') - custom = factory.Faker('pyboolean') + amount = factory.Faker('pydecimal', left_digits=3, positive=True) + paid_by_house = factory.Faker('pybool') + custom = factory.Faker('pybool') class UseFactory(factory.DjangoModelFactory): @@ -105,7 +96,7 @@ class Meta: updated = factory.Faker('past_datetime') comments = factory.Faker('paragraph') - rate = factory.Faker('pydecimal') + rate = factory.Faker('pydecimal', left_digits=3, positive=True) uuid = factory.Faker('uuid4') bill = factory.SubFactory(BookingBillFactory) @@ -132,7 +123,7 @@ class Meta: payment_date = factory.Faker('past_datetime') # payment_service and payment_method may be empty so ignoring those - paid_amount = factory.Faker('pydecimal') + paid_amount = factory.Faker('pydecimal', left_digits=3, positive=True) transaction_id = factory.Faker('uuid4') last4 = factory.Faker('pyint') diff --git a/core/factory_apps/user.py b/core/factory_apps/user.py index 169601ef..53345a2f 100644 --- a/core/factory_apps/user.py +++ b/core/factory_apps/user.py @@ -10,10 +10,10 @@ class Meta: model = User username = factory.Faker('name') - first_name = factory.Faker('name') - last_name = factory.Faker('lastname') + first_name = factory.Faker('first_name') + last_name = factory.Faker('last_name') email = factory.Faker('email') - is_staff = factory.Faker('pyboolean') + is_staff = factory.Faker('pybool') is_active = True is_superuser = False From dffaafea3ff91d50503e40e0e0fa62994ade9a7b Mon Sep 17 00:00:00 2001 From: Jonathan Sundqvist Date: Wed, 31 Oct 2018 00:24:16 +0100 Subject: [PATCH 13/29] Command to generate test data --- core/management/commands/__init__.py | 0 .../management/commands/generate_test_data.py | 67 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 core/management/commands/__init__.py create mode 100644 core/management/commands/generate_test_data.py diff --git a/core/management/commands/__init__.py b/core/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/core/management/commands/generate_test_data.py b/core/management/commands/generate_test_data.py new file mode 100644 index 00000000..1cefdeb7 --- /dev/null +++ b/core/management/commands/generate_test_data.py @@ -0,0 +1,67 @@ +import contextlib +import io + +from django.core.management.base import BaseCommand +from django.core.management import call_command +from django.db import connection + +from faker import Faker + +from core.factory_apps import location +from core.factory_apps import communication +from core.factory_apps import events +from core.factory_apps import payment + + +class Command(BaseCommand): + help = 'Closes the specified poll for voting' + + def handle(self, *args, **options): + f = io.StringIO() + with contextlib.redirect_stdout(f): + call_command('sqlflush') + flush_db = f.getvalue() + + with connection.cursor() as cursor: + statements = flush_db.split('\n') + for statement in statements: + cursor.execute(statement) + + call_command('migrate') + + # setting the seed so output is consistent + fake = Faker() + fake.seed(1) + # Building location specific things + locationobj = location.LocationFactory() + resource = location.ResourceFactory(location=locationobj) + location.LocationFee(location=locationobj) + + menu = location.LocationMenuFactory(location=locationobj) + location.LocationFlatPageFactory(menu=menu) + + location.CapacityChangeFactory(resource=resource) + + # event things + event_admin = events.EventAdminGroupFactory(location=locationobj) + event_series = events.EventSeriesFactory() + events.EventFactory( + location=locationobj, admin=event_admin, series=event_series + ) + events.EventNotificationFactory() + + # communication + communication.EmailtemplateFactory() + + # payment + subscription = payment.SubscriptionFactory(location=locationobj) + payment.SubscriptionBillFactory(subscription=subscription) + + bill = payment.BookingBillFactory() + payment.BillLineItem(bill=bill) + use = payment.UseFactory(location=locationobj, resource=resource) + payment.BookingFactory(bill=bill, use=use) + + payment.PaymentFactory(bill=bill) + + self.stdout.write(self.style.SUCCESS('Successfully generated testdata')) From 102f52e1bd0cd3b9dfcaf40a9d4109a1244cd033 Mon Sep 17 00:00:00 2001 From: Jonathan Sundqvist Date: Wed, 31 Oct 2018 00:35:05 +0100 Subject: [PATCH 14/29] Fix bug with with location url that allows hyphen in url --- core/urls/location.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/urls/location.py b/core/urls/location.py index f53dd6ff..683d0e79 100644 --- a/core/urls/location.py +++ b/core/urls/location.py @@ -46,5 +46,5 @@ urlpatterns = patterns('core.views.unsorted', url(r'^$', 'location_list', name='location_list'), - url(r'^(?P\w+)/', include(per_location_patterns)), + url(r'^(?P[\w-]+)/', include(per_location_patterns)), ) From 2cb29ab1c6a85ed3534d2c91a27b1c56222cf978 Mon Sep 17 00:00:00 2001 From: Jonathan Sundqvist Date: Wed, 31 Oct 2018 00:55:36 +0100 Subject: [PATCH 15/29] Remove unused RoomImage and LocationImage factory model --- core/factory_apps/location.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/core/factory_apps/location.py b/core/factory_apps/location.py index 24115cb8..a713228b 100644 --- a/core/factory_apps/location.py +++ b/core/factory_apps/location.py @@ -144,13 +144,5 @@ class Meta: accept_drft = factory.Faker('pybool') -class LocationImageFactory(factory.DjangoModelFactory): - pass - - -class RoomImageFactory(factory.DjangoModelFactory): - pass - - class BackingFactory(factory.DjangoModelFactory): pass From 2344082fb9a354354f1362583be9e65e6d6dfff5 Mon Sep 17 00:00:00 2001 From: Jonathan Sundqvist Date: Sun, 4 Nov 2018 09:52:13 +0100 Subject: [PATCH 16/29] Sorting out errors for postgres and adding test --- api/tests/commands/test_data_generation.py | 8 ++++++++ core/factory_apps/events.py | 2 +- core/factory_apps/location.py | 2 +- core/management/commands/generate_test_data.py | 2 ++ docs/how-to-run.md | 5 +++-- 5 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 api/tests/commands/test_data_generation.py diff --git a/api/tests/commands/test_data_generation.py b/api/tests/commands/test_data_generation.py new file mode 100644 index 00000000..c3e893a1 --- /dev/null +++ b/api/tests/commands/test_data_generation.py @@ -0,0 +1,8 @@ +from django.test import TransactionTestCase +from django.core.management import call_command + + +class DataGenerationTest(TransactionTestCase): + + def test_run_data_generation(self): + call_command('generate_test_data') diff --git a/core/factory_apps/events.py b/core/factory_apps/events.py index 22879203..00a043b1 100644 --- a/core/factory_apps/events.py +++ b/core/factory_apps/events.py @@ -57,7 +57,7 @@ class Meta: organizer_notes = factory.Faker('paragraph') - limit = factory.Faker('random_digit_or_empty') + limit = factory.Faker('random_digit') visibility = Event.PUBLIC status = Event.PENDING diff --git a/core/factory_apps/location.py b/core/factory_apps/location.py index a713228b..bfa2322b 100644 --- a/core/factory_apps/location.py +++ b/core/factory_apps/location.py @@ -107,7 +107,7 @@ class Meta: model = LocationMenu location = factory.SubFactory(LocationFactory) - name = factory.Faker('text') + name = factory.Faker('text', max_nb_chars=15) class FlatpageFactory(factory.DjangoModelFactory): diff --git a/core/management/commands/generate_test_data.py b/core/management/commands/generate_test_data.py index 1cefdeb7..559fedfd 100644 --- a/core/management/commands/generate_test_data.py +++ b/core/management/commands/generate_test_data.py @@ -25,6 +25,8 @@ def handle(self, *args, **options): with connection.cursor() as cursor: statements = flush_db.split('\n') for statement in statements: + if not statement: + continue cursor.execute(statement) call_command('migrate') diff --git a/docs/how-to-run.md b/docs/how-to-run.md index c28c5edd..f2f34592 100644 --- a/docs/how-to-run.md +++ b/docs/how-to-run.md @@ -70,11 +70,12 @@ create your own local_settings.py file from local_settings.example.py. inside th - browse through settings.py. make note of the location of the media directory and media_url, and any other settings of interest. -## initialisation +## Initialisation go back into the top level repository directory and do the following: -- `./manage.py migrate` will initialise the database on first run +- `./manage.py migrate` will initialise the database on first run. In the next step you want to get some initial data into the database. +- `./manage.py generate_test_data`. This will populate the database with some randomized initial data. ## Run! From 4fa52c19fca525c14ec424bc35a7f4502f3d4d91 Mon Sep 17 00:00:00 2001 From: Jonathan Sundqvist Date: Sat, 20 Oct 2018 14:51:10 +0200 Subject: [PATCH 17/29] Settings draft --- modernomad/settings/__init__.py | 0 modernomad/settings/common.py | 238 ++++++++++++++++++++++++++++++ modernomad/settings/local.py | 20 +++ modernomad/settings/production.py | 7 + modernomad/settings/staging.py | 0 5 files changed, 265 insertions(+) create mode 100644 modernomad/settings/__init__.py create mode 100644 modernomad/settings/common.py create mode 100644 modernomad/settings/local.py create mode 100644 modernomad/settings/production.py create mode 100644 modernomad/settings/staging.py diff --git a/modernomad/settings/__init__.py b/modernomad/settings/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/modernomad/settings/common.py b/modernomad/settings/common.py new file mode 100644 index 00000000..65f1a7ef --- /dev/null +++ b/modernomad/settings/common.py @@ -0,0 +1,238 @@ +# Django settings for modernomad project. +import os +import datetime +import sys +from pathlib import Path + +BASE_DIR = Path.cwd() +BACKUP_ROOT = BASE_DIR / 'backups' + +ADMINS = ( + ('Jessy Kate Schingler', 'jessy@embassynetwork.com'), +) +MANAGERS = ADMINS + +DEBUG = False +TEMPLATE_DEBUG = False + +SECRET_KEY = os.getenv('SECRET_KEY', 'mysecret') +STRIPE_PUBLISHABLE_KEY = os.getenv('STRIPE_PUBLISHABLE_KEY', default='') +STRIPE_SECRET_KEY = os.getenv('STRIPE_SECRET_KEY', '') + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'USER': 'postgres', + 'NAME': os.environ['POSTGRES_NAME'], + 'PASSWORD': os.environ['POSTGRES_PASSWORD'], + } +} + +# Local time zone for this installation. Choices can be found here: +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +TIME_ZONE = 'America/Los_Angeles' + +# Language code for this installation. All choices can be found here: +# http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGE_CODE = 'en-us' + +SITE_ID = 1 + +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = True + +# If you set this to False, Django will not format dates, numbers and +# calendars according to the current locale. +USE_L10N = True + +# If you set this to False, Django will not use timezone-aware datetimes. +USE_TZ = True + +# Absolute filesystem path to the directory that will hold user-uploaded files. +# Example: "/home/media/media.lawrence.com/media/" +MEDIA_ROOT = BASE_DIR / 'media' + +# URL that handles the media served from MEDIA_ROOT. Make sure to use a +# trailing slash. +# Examples: "http://media.lawrence.com/media/", "http://example.com/media/" +MEDIA_URL = "/media/" + +# Absolute path to the directory static files should be collected to. +# Don't put anything in this directory yourself; store your static files +# in apps' "static/" subdirectories and in STATICFILES_DIRS. +# Example: "/home/media/media.lawrence.com/static/" +STATIC_ROOT = BASE_DIR / 'static' +STATICFILES_DIRS = ('static', 'client/dist') +STATIC_URL = '/static/' +STATICFILES_FINDERS = ( + 'django.contrib.staticfiles.finders.FileSystemFinder', + 'django.contrib.staticfiles.finders.AppDirectoriesFinder', + # 'django.contrib.staticfiles.finders.DefaultStorageFinder', + 'compressor.finders.CompressorFinder', +) + +AUTHENTICATION_BACKENDS = ( + 'modernomad.backends.EmailOrUsernameModelBackend', + 'django.contrib.auth.backends.ModelBackend' +) + +EMAIL_BACKEND = 'modernomad.backends.MailgunBackend' +MAILGUN_API_KEY = os.getenv('MAILGUN_APIKEY', '') +# this will be used as the subject line prefix for all emails sent from this app. +EMAIL_SUBJECT_PREFIX = os.getenv('EMAIL_SUBJECT', '[Embassy Network] ') +DEFAULT_FROM_EMAIL = os.getenv('EMAIL_FROM', 'stay@embassynetwork.com') +LIST_DOMAIN = "mail.embassynetwork.com" + +GOOGLE_ANALYTICS_PROPERTY_ID = os.getenv('GA_PROPERTY_ID', '') +GOOGLE_ANALYTICS_DOMAIN = 'embassynetwork.com' + +MIDDLEWARE_CLASSES = ( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware', + 'modernomad.middleware.crossdomainxhr.CORSMiddleware', + # Uncomment the next line for simple clickjacking protection: + # 'django.middleware.clickjacking.XFrameOptionsMiddleware', +) + +# List of callables that know how to import templates from various sources. +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', + # 'django.template.loaders.eggs.Loader', +) +# default template context processors +TEMPLATE_CONTEXT_PROCESSORS = ( + "django.contrib.auth.context_processors.auth", + "django.core.context_processors.debug", + "django.core.context_processors.i18n", + "django.core.context_processors.media", + "django.core.context_processors.static", + "django.core.context_processors.tz", + "django.core.context_processors.request", + "django.contrib.messages.context_processors.messages", + "core.context_processors.location.location_variables", + "core.context_processors.location.network_locations", + "core.context_processors.analytics.google_analytics", +) + +TEMPLATE_DIRS = ( + # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. + BASE_DIR / 'templates', + BASE_DIR / 'core' / 'templates' +) + +# other JWT options available at https://github.com/jpadilla/django-jwt-auth +JWT_EXPIRATION_DELTA = datetime.timedelta(days=1000) + +ROOT_URLCONF = 'modernomad.urls.main' + +# Python dotted path to the WSGI application used by Django's runserver. +WSGI_APPLICATION = 'modernomad.wsgi.application' + +WEBPACK_LOADER = { + 'DEFAULT': { + 'BUNDLE_DIR_NAME': 'client/build/', + 'STATS_FILE': BASE_DIR / 'client' / 'webpack-stats.json', + } +} + +INSTALLED_APPS = [ + # django stuff + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'django.contrib.admin', + 'django.contrib.flatpages', + 'django.contrib.admindocs', + 'django.contrib.humanize', + + # 3rd party + 'compressor', + 'django_behave', + 'django_extensions', + 'django_filters' + 'django_graphiql', + 'djcelery', + 'graphene_django', + 'rest_framework', + 'webpack_loader', + + # modernomad + 'core', + 'bank', + 'gather', + 'modernomad', + 'api', + 'bdd', + 'graphapi', +] + +COMPRESS_PRECOMPILERS = ( + ('text/less', 'lessc {infile} {outfile}'), +) + +# FIXME: disabled. see https://github.com/embassynetwork/modernomad/issues/302 +# TEST_RUNNER = 'django_behave.runner.DjangoBehaveTestSuiteRunner' + +AUTH_PROFILE_MODULE = 'core.UserProfile' +ACCOUNT_ACTIVATION_DAYS = 7 # One week account activation window. + +# Discourse discussion group +DISCOURSE_BASE_URL = 'http://your-discourse-site.com' +DISCOURSE_SSO_SECRET = 'paste_your_secret_here' + +# If we add a page for the currently-logged-in user to view and edit +# their profile, we might want to use that here instead. +LOGIN_REDIRECT_URL = '/' +LOGIN_URL = '/people/login/' +LOGOUT_URL = '/people/logout/' + +# Celery configuration options +BROKER_URL = "amqp://guest:guest@localhost:5672//" +CELERY_TASK_SERIALIZER = 'json' +CELERY_RESULT_SERIALIZER = 'json' +CELERY_ENABLE_UTC = True +CELERY_ACCEPT_CONTENT = ['json', 'yaml'] + +REST_FRAMEWORK = { + 'DEFAULT_PERMISSION_CLASSES': [ + 'rest_framework.permissions.IsAuthenticated', + ] +} + +NOSE_ARGS = [ + '--nocapture', + '--nologcapture' +] + + +class DisableMigrations(object): + def __contains__(self, item): + return True + + def __getitem__(self, item): + return None + + +TESTS_IN_PROGRESS = False +if 'test' in sys.argv[1:]: + PASSWORD_HASHERS = ( + 'django.contrib.auth.hashers.MD5PasswordHasher', + ) + TESTS_IN_PROGRESS = True + MIGRATION_MODULES = DisableMigrations() + +os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = "localhost:8000-8010,8080,9200-9300" + +# Setting unique=True on a ForeignKey has the same effect as using a OneToOneField. +SILENCED_SYSTEM_CHECKS = ["fields.W342"] diff --git a/modernomad/settings/local.py b/modernomad/settings/local.py new file mode 100644 index 00000000..856f4959 --- /dev/null +++ b/modernomad/settings/local.py @@ -0,0 +1,20 @@ +from .common import * # noqa +from .common import INSTALLED_APPS + +DEBUG = True +TEMPLATE_DEBUG = True +INSTALLED_APPS = INSTALLED_APPS + [ + 'debug_toolbar' +] + + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. + 'NAME': 'modernomad.db', # Or path to database file if using sqlite3. + 'USER': '', # Not used with sqlite3. + 'PASSWORD': '', # Not used with sqlite3. + 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. + 'PORT': '', # Set to empty string for default. Not used with sqlite3. + } +} diff --git a/modernomad/settings/production.py b/modernomad/settings/production.py new file mode 100644 index 00000000..8027ad46 --- /dev/null +++ b/modernomad/settings/production.py @@ -0,0 +1,7 @@ +from .common import * + +ALLOWED_HOSTS = [ + 'www.embassynetwork.com', + 'embassynetwork.com', + 'map.chozadelmundo.com' +] diff --git a/modernomad/settings/staging.py b/modernomad/settings/staging.py new file mode 100644 index 00000000..e69de29b From 2d00654d8f5cdb7d78d74e0670d0d8ad7607ad41 Mon Sep 17 00:00:00 2001 From: Jonathan Sundqvist Date: Mon, 22 Oct 2018 19:20:03 +0200 Subject: [PATCH 18/29] Modify new settings, hattip to @bfirsh --- modernomad/settings/common.py | 107 +++++++++++++++++++++++------- modernomad/settings/local.py | 1 + modernomad/settings/production.py | 34 ++++++++-- modernomad/settings/staging.py | 4 ++ 4 files changed, 117 insertions(+), 29 deletions(-) diff --git a/modernomad/settings/common.py b/modernomad/settings/common.py index 65f1a7ef..f3181d4d 100644 --- a/modernomad/settings/common.py +++ b/modernomad/settings/common.py @@ -3,29 +3,29 @@ import datetime import sys from pathlib import Path +import environ + +env = environ.Env() BASE_DIR = Path.cwd() BACKUP_ROOT = BASE_DIR / 'backups' -ADMINS = ( - ('Jessy Kate Schingler', 'jessy@embassynetwork.com'), -) +ADMINS = (( + env('ADMIN_NAME', default='Unnamed'), + env('ADMIN_EMAIL', default='none@example.com') +),) + MANAGERS = ADMINS +ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', default=[]) DEBUG = False TEMPLATE_DEBUG = False -SECRET_KEY = os.getenv('SECRET_KEY', 'mysecret') -STRIPE_PUBLISHABLE_KEY = os.getenv('STRIPE_PUBLISHABLE_KEY', default='') -STRIPE_SECRET_KEY = os.getenv('STRIPE_SECRET_KEY', '') +STRIPE_PUBLISHABLE_KEY = env('STRIPE_PUBLISHABLE_KEY', default='') +STRIPE_SECRET_KEY = env('STRIPE_SECRET_KEY', '') DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'USER': 'postgres', - 'NAME': os.environ['POSTGRES_NAME'], - 'PASSWORD': os.environ['POSTGRES_PASSWORD'], - } + 'default': env.db('DATABASE_URL', default='postgres://postgres@postgres/postgres'), } # Local time zone for this installation. Choices can be found here: @@ -58,6 +58,14 @@ # Examples: "http://media.lawrence.com/media/", "http://example.com/media/" MEDIA_URL = "/media/" +AWS_STORAGE_BUCKET_NAME = env('AWS_STORAGE_BUCKET_NAME', default='') +AWS_ACCESS_KEY_ID = env('AWS_ACCESS_KEY_ID', default='') +AWS_SECRET_ACCESS_KEY = env('AWS_SECRET_ACCESS_KEY', default='') +AWS_DEFAULT_ACL = 'public-read' +if AWS_STORAGE_BUCKET_NAME: + DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage' + MEDIA_URL = env('MEDIA_URL', default='https://%s.s3.amazonaws.com/' % AWS_STORAGE_BUCKET_NAME) + # Absolute path to the directory static files should be collected to. # Don't put anything in this directory yourself; store your static files # in apps' "static/" subdirectories and in STATICFILES_DIRS. @@ -78,14 +86,15 @@ ) EMAIL_BACKEND = 'modernomad.backends.MailgunBackend' -MAILGUN_API_KEY = os.getenv('MAILGUN_APIKEY', '') +MAILGUN_API_KEY = env('MAILGUN_API_KEY', default='') + # this will be used as the subject line prefix for all emails sent from this app. -EMAIL_SUBJECT_PREFIX = os.getenv('EMAIL_SUBJECT', '[Embassy Network] ') -DEFAULT_FROM_EMAIL = os.getenv('EMAIL_FROM', 'stay@embassynetwork.com') -LIST_DOMAIN = "mail.embassynetwork.com" +EMAIL_SUBJECT_PREFIX = env('EMAIL_SUBJECT_PREFIX', default='[Modernomad] ') +DEFAULT_FROM_EMAIL = env('DEFAULT_FROM_EMAIL', default='stay@example.com') +LIST_DOMAIN = env('LIST_DOMAIN', default='somedomain.com') -GOOGLE_ANALYTICS_PROPERTY_ID = os.getenv('GA_PROPERTY_ID', '') -GOOGLE_ANALYTICS_DOMAIN = 'embassynetwork.com' +GOOGLE_ANALYTICS_PROPERTY_ID = env('GOOGLE_ANALYTICS_PROPERTY_ID', default='') +GOOGLE_ANALYTICS_DOMAIN = env('GOOGLE_ANALYTICS_DOMAIN', default='example.com') MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', @@ -187,10 +196,6 @@ AUTH_PROFILE_MODULE = 'core.UserProfile' ACCOUNT_ACTIVATION_DAYS = 7 # One week account activation window. -# Discourse discussion group -DISCOURSE_BASE_URL = 'http://your-discourse-site.com' -DISCOURSE_SSO_SECRET = 'paste_your_secret_here' - # If we add a page for the currently-logged-in user to view and edit # their profile, we might want to use that here instead. LOGIN_REDIRECT_URL = '/' @@ -198,11 +203,15 @@ LOGOUT_URL = '/people/logout/' # Celery configuration options -BROKER_URL = "amqp://guest:guest@localhost:5672//" +BROKER_URL = env( + 'BROKER_URL', + default=env('CLOUDAMQP_URL', default='amqp://guest:guest@localhost:5672//') +) CELERY_TASK_SERIALIZER = 'json' CELERY_RESULT_SERIALIZER = 'json' CELERY_ENABLE_UTC = True CELERY_ACCEPT_CONTENT = ['json', 'yaml'] +CELERY_RESULT_BACKEND = BROKER_URL REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': [ @@ -215,6 +224,58 @@ '--nologcapture' ] +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'formatters': { + 'verbose': { + 'format': "[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s", + 'datefmt': "%d/%b/%Y %H:%M:%S" + }, + 'simple': { + 'format': '%(levelname)s %(message)s' + }, + }, + 'handlers': { + 'file': { + 'level': 'DEBUG', + 'class': 'logging.FileHandler', + 'filename': './django.log', + 'formatter': 'verbose', + }, + 'mail_admins': { + 'level': 'ERROR', + 'class': 'django.utils.log.AdminEmailHandler', + 'include_html': True, + 'formatter': 'verbose', + } + }, + 'loggers': { + 'django': { + 'handlers': ['file'], + 'level': 'INFO', + 'propagate': True, + }, + 'django.request': { + 'handlers': ['file', 'mail_admins'], + 'level': 'INFO', + 'propagate': True, + }, + 'core': { + 'handlers': ['file'], + 'level': 'DEBUG', + }, + 'modernomad': { + 'handlers': ['file'], + 'level': 'DEBUG', + }, + 'gather': { + 'handlers': ['file'], + 'level': 'DEBUG', + }, + }, +} + class DisableMigrations(object): def __contains__(self, item): diff --git a/modernomad/settings/local.py b/modernomad/settings/local.py index 856f4959..d62c50b7 100644 --- a/modernomad/settings/local.py +++ b/modernomad/settings/local.py @@ -7,6 +7,7 @@ 'debug_toolbar' ] +EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' DATABASES = { 'default': { diff --git a/modernomad/settings/production.py b/modernomad/settings/production.py index 8027ad46..884f087c 100644 --- a/modernomad/settings/production.py +++ b/modernomad/settings/production.py @@ -1,7 +1,29 @@ -from .common import * +from .common import * # noqa +from .common import env +from .common import BASE_DIR -ALLOWED_HOSTS = [ - 'www.embassynetwork.com', - 'embassynetwork.com', - 'map.chozadelmundo.com' -] +SECRET_KEY = env('SECRET_KEY') + +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'handlers': { + 'console': { + 'class': 'logging.StreamHandler', + }, + }, + 'loggers': { + 'django': { + 'handlers': ['console'], + 'level': env('DJANGO_LOG_LEVEL', default='INFO'), + }, + }, +} + +WEBPACK_LOADER = { + 'DEFAULT': { + 'BUNDLE_DIR_NAME': '', + 'CACHE': True, + 'STATS_FILE': BASE_DIR / 'client/webpack-stats-prod.json', + } +} diff --git a/modernomad/settings/staging.py b/modernomad/settings/staging.py index e69de29b..67424445 100644 --- a/modernomad/settings/staging.py +++ b/modernomad/settings/staging.py @@ -0,0 +1,4 @@ +from .common import * # noqa +from .common import env + +SECRET_KEY = env('SECRET_KEY') From 4f77c6615b957239212f6fb6e7923dd0fd7a157c Mon Sep 17 00:00:00 2001 From: Jonathan Sundqvist Date: Mon, 22 Oct 2018 19:20:27 +0200 Subject: [PATCH 19/29] Delete all old settings --- modernomad/local_settings.example.py | 138 ---------------- modernomad/local_settings.travis.py | 140 ---------------- modernomad/settings.py | 236 --------------------------- modernomad/settings_docker.py | 98 ----------- 4 files changed, 612 deletions(-) delete mode 100644 modernomad/local_settings.example.py delete mode 100644 modernomad/local_settings.travis.py delete mode 100644 modernomad/settings.py delete mode 100644 modernomad/settings_docker.py diff --git a/modernomad/local_settings.example.py b/modernomad/local_settings.example.py deleted file mode 100644 index 4badf22d..00000000 --- a/modernomad/local_settings.example.py +++ /dev/null @@ -1,138 +0,0 @@ -# copy this file to local_settings.py. it should be exluded from the repo -# (ensure local_settings.py is in .gitignore) - -# Make this unique, and don't share it with anybody. -SECRET_KEY = 'secret' - -ADMINS = ( - ('Your Name', 'your@email.com'), - ) - -ALLOWED_HOSTS = ['domain.com'] - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'USER': 'postgres', - 'NAME': 'modernomadb', - 'PASSWORD': 'somepassword', - } -} - -MEDIA_ROOT = "../media" -BACKUP_ROOT = "../backups/" -BACKUP_COUNT = 30 - -# use XS_SHARING_ALLOWED_ORIGINS = '*' for all domains -XS_SHARING_ALLOWED_ORIGINS = "http://localhost:8989/" -XS_SHARING_ALLOWED_METHODS = ['POST', 'GET', 'PUT', 'OPTIONS', 'DELETE'] -XS_SHARING_ALLOWED_HEADERS = ["Content-Type"] - -# what mode are we running in? use this to trigger different settings. -DEVELOPMENT = 0 -PRODUCTION = 1 - -# default mode is dev. change to production as appropriate. -MODE = DEVELOPMENT - -# how many days should people be allowed to make a booking request for? -MAX_BOOKING_DAYS = 14 - -# how many days ahead to send the welcome email to guests with relevan house -# info. -WELCOME_EMAIL_DAYS_AHEAD = 2 - -# this should be a TEST or PRODUCTION key depending on whether this is a local -# test/dev site or production! -STRIPE_SECRET_KEY = "sk_XXXXX" -STRIPE_PUBLISHABLE_KEY = "pk_XXXXX" - -# Discourse discussion group -DISCOURSE_BASE_URL = 'http://your-discourse-site.com' -DISCOURSE_SSO_SECRET = 'paste_your_secret_here' - -MAILGUN_API_KEY = "key-XXXX" - -LIST_DOMAIN = "somedomain.com" - -if MODE == DEVELOPMENT: - EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' - DEBUG = True -else: - EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' - EMAIL_USE_TLS = True - EMAIL_HOST = 'somehost' - EMAIL_PORT = 587 - EMAIL_HOST_USER = 'some@email.com' - EMAIL_HOST_PASSWORD = 'password' - DEBUG = False - -TEMPLATE_DEBUG = DEBUG - -# fill in any local template directories. any templates with the same name WILL -# OVERRIDE included templates. don't forget the trailing slash in the path, and -# a comma at the end of the tuple item if there is only one path. -LOCAL_TEMPLATE_DIRS = ( - # eg, "../local_templates/", - ) - -# celery configuration options -BROKER_URL = 'amqp://' -CELERY_RESULT_BACKEND = 'amqp://' - -CELERY_TASK_SERIALIZER = 'json' -CELERY_RESULT_SERIALIZER = 'json' -CELERY_ENABLE_UTC = True - -# Logging -LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'formatters': { - 'verbose': { - 'format': "[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s", - 'datefmt': "%d/%b/%Y %H:%M:%S" - }, - 'simple': { - 'format': '%(levelname)s %(message)s' - }, - }, - 'handlers': { - 'file': { - 'level': 'DEBUG', - 'class': 'logging.FileHandler', - 'filename': '/Users/jessykate/code/embassynetwork/logs/django.log', - 'formatter': 'verbose', - }, - 'mail_admins': { - 'level': 'ERROR', - 'class': 'django.utils.log.AdminEmailHandler', - 'include_html': True, - 'formatter': 'verbose', - } - }, - 'loggers': { - 'django': { - 'handlers': ['file'], - 'level': 'INFO', - 'propagate': True, - }, - 'django.request': { - 'handlers': ['file', 'mail_admins'], - 'level': 'INFO', - 'propagate': True, - }, - 'core': { - 'handlers': ['file'], - 'level': 'DEBUG', - }, - 'modernomad': { - 'handlers': ['file'], - 'level': 'DEBUG', - }, - 'gather': { - 'handlers': ['file'], - 'level': 'DEBUG', - }, - }, -} diff --git a/modernomad/local_settings.travis.py b/modernomad/local_settings.travis.py deleted file mode 100644 index 891936cf..00000000 --- a/modernomad/local_settings.travis.py +++ /dev/null @@ -1,140 +0,0 @@ -import os - -# copy this file to local_settings.py. it should be exluded from the repo -# (ensure local_settings.py is in .gitignore) - -# Make this unique, and don't share it with anybody. -SECRET_KEY = 'secret' - -ADMINS = ( - ('Craig Ambrose', 'craig@enspiral.com') -) - -ALLOWED_HOSTS = ['domain.com'] - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'USER': 'postgres', - 'NAME': 'modernomadb' - } -} - -MEDIA_ROOT = "../media" -BACKUP_ROOT = "../backups/" -BACKUP_COUNT = 30 - -# use XS_SHARING_ALLOWED_ORIGINS = '*' for all domains -XS_SHARING_ALLOWED_ORIGINS = "http://localhost:8989/" -XS_SHARING_ALLOWED_METHODS = ['POST', 'GET', 'PUT', 'OPTIONS', 'DELETE'] -XS_SHARING_ALLOWED_HEADERS = ["Content-Type"] - -# what mode are we running in? use this to trigger different settings. -DEVELOPMENT = 0 -PRODUCTION = 1 - -# default mode is dev. change to production as appropriate. -MODE = DEVELOPMENT - -# how many days should people be allowed to make a booking request for? -MAX_BOOKING_DAYS = 14 - -# how many days ahead to send the welcome email to guests with relevan house -# info. -WELCOME_EMAIL_DAYS_AHEAD = 2 - -# this should be a TEST or PRODUCTION key depending on whether this is a local -# test/dev site or production! -STRIPE_SECRET_KEY = "sk_XXXXX" -STRIPE_PUBLISHABLE_KEY = "pk_XXXXX" - -# Discourse discussion group -DISCOURSE_BASE_URL = 'http://your-discourse-site.com' -DISCOURSE_SSO_SECRET = 'paste_your_secret_here' - -MAILGUN_API_KEY = "key-XXXX" - -LIST_DOMAIN = "somedomain.com" - -EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' -DEBUG = True - -TEMPLATE_DEBUG = DEBUG - -# fill in any local template directories. any templates with the same name WILL -# OVERRIDE included templates. don't forget the trailing slash in the path, and -# a comma at the end of the tuple item if there is only one path. -LOCAL_TEMPLATE_DIRS = ( - # eg, "../local_templates/", - ) - -# celery configuration options -BROKER_URL = 'amqp://' -CELERY_RESULT_BACKEND = 'amqp://' - -CELERY_TASK_SERIALIZER = 'json' -CELERY_RESULT_SERIALIZER = 'json' -CELERY_ENABLE_UTC = True - -ROOT = os.path.dirname(os.path.abspath(__file__)) -BASE_DIR = os.path.normpath(ROOT + '/..') - -WEBPACK_LOADER = { - 'DEFAULT': { - 'BUNDLE_DIR_NAME': '', - 'STATS_FILE': os.path.join(BASE_DIR, 'client/webpack-stats-prod.json'), - } -} - -# Logging -LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'formatters': { - 'verbose': { - 'format': "[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s", - 'datefmt': "%d/%b/%Y %H:%M:%S" - }, - 'simple': { - 'format': '%(levelname)s %(message)s' - }, - }, - 'handlers': { - 'file': { - 'level': 'DEBUG', - 'class': 'logging.FileHandler', - 'filename': './django.log', - 'formatter': 'verbose', - }, - 'mail_admins': { - 'level': 'ERROR', - 'class': 'django.utils.log.AdminEmailHandler', - 'include_html': True, - 'formatter': 'verbose', - } - }, - 'loggers': { - 'django': { - 'handlers': ['file'], - 'level': 'INFO', - 'propagate': True, - }, - 'django.request': { - 'handlers': ['file', 'mail_admins'], - 'level': 'INFO', - 'propagate': True, - }, - 'core': { - 'handlers': ['file'], - 'level': 'DEBUG', - }, - 'modernomad': { - 'handlers': ['file'], - 'level': 'DEBUG', - }, - 'gather': { - 'handlers': ['file'], - 'level': 'DEBUG', - }, - }, -} diff --git a/modernomad/settings.py b/modernomad/settings.py deleted file mode 100644 index ab7d081d..00000000 --- a/modernomad/settings.py +++ /dev/null @@ -1,236 +0,0 @@ -# Django settings for modernomad project. -import os -import datetime -import sys - -# Make filepaths relative to settings. -ROOT = os.path.dirname(os.path.abspath(__file__)) -BASE_DIR = os.path.normpath(ROOT + '/..') - -path = lambda *a: os.path.join(ROOT, *a) - -BACKUP_ROOT = ROOT + '/backups/' - -ADMINS = ( - ('Jessy Kate Schingler', 'jessy@embassynetwork.com'), -) - -DEBUG = os.getenv('DEBUG', False) -STRIPE_PUBLISHABLE_KEY = os.getenv('STRIPE_PUBLISHABLE_KEY', default='') - -MANAGERS = ADMINS - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. - 'NAME': 'modernomad.db', # Or path to database file if using sqlite3. - 'USER': '', # Not used with sqlite3. - 'PASSWORD': '', # Not used with sqlite3. - 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. - 'PORT': '', # Set to empty string for default. Not used with sqlite3. - } -} - -# Local time zone for this installation. Choices can be found here: -# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name -TIME_ZONE = 'America/Los_Angeles' - -# Language code for this installation. All choices can be found here: -# http://www.i18nguy.com/unicode/language-identifiers.html -LANGUAGE_CODE = 'en-us' - -SITE_ID = 1 - -# If you set this to False, Django will make some optimizations so as not -# to load the internationalization machinery. -USE_I18N = True - -# If you set this to False, Django will not format dates, numbers and -# calendars according to the current locale. -USE_L10N = True - -# If you set this to False, Django will not use timezone-aware datetimes. -USE_TZ = True - -# Absolute filesystem path to the directory that will hold user-uploaded files. -# Example: "/home/media/media.lawrence.com/media/" -MEDIA_ROOT = path("../media/") - -# URL that handles the media served from MEDIA_ROOT. Make sure to use a -# trailing slash. -# Examples: "http://media.lawrence.com/media/", "http://example.com/media/" -MEDIA_URL = "/media/" - -# Absolute path to the directory static files should be collected to. -# Don't put anything in this directory yourself; store your static files -# in apps' "static/" subdirectories and in STATICFILES_DIRS. -# Example: "/home/media/media.lawrence.com/static/" -STATIC_ROOT = path("../../static/") -STATICFILES_DIRS = ('static', 'client/dist') -STATIC_URL = '/static/' -STATICFILES_FINDERS = ( - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', - # 'django.contrib.staticfiles.finders.DefaultStorageFinder', - 'compressor.finders.CompressorFinder', -) - -AUTHENTICATION_BACKENDS = ( - 'modernomad.backends.EmailOrUsernameModelBackend', - 'django.contrib.auth.backends.ModelBackend' -) - -EMAIL_BACKEND = 'modernomad.backends.MailgunBackend' - -# List of callables that know how to import templates from various sources. -TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', - # 'django.template.loaders.eggs.Loader', -) - -MIDDLEWARE_CLASSES = ( - 'django.middleware.common.CommonMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'modernomad.middleware.crossdomainxhr.CORSMiddleware', - 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware', - # Uncomment the next line for simple clickjacking protection: - # 'django.middleware.clickjacking.XFrameOptionsMiddleware', -) - -# default template context processors -TEMPLATE_CONTEXT_PROCESSORS = ( - "django.contrib.auth.context_processors.auth", - "django.core.context_processors.debug", - "django.core.context_processors.i18n", - "django.core.context_processors.media", - "django.core.context_processors.static", - "django.core.context_processors.tz", - "django.core.context_processors.request", - "django.contrib.messages.context_processors.messages", - "core.context_processors.location.location_variables", - "core.context_processors.location.network_locations", - "core.context_processors.analytics.google_analytics", -) - -# other JWT options available at https://github.com/jpadilla/django-jwt-auth -JWT_EXPIRATION_DELTA = datetime.timedelta(days=1000) - -ROOT_URLCONF = 'modernomad.urls.main' - -# Python dotted path to the WSGI application used by Django's runserver. -WSGI_APPLICATION = 'modernomad.wsgi.application' - -TEMPLATE_DIRS = ( - # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". - # Always use forward slashes, even on Windows. - # Don't forget to use absolute paths, not relative paths. - path("../templates/"), - path("core/templates/"), -) - -WEBPACK_LOADER = { - 'DEFAULT': { - 'BUNDLE_DIR_NAME': 'client/build/', - 'STATS_FILE': os.path.join(BASE_DIR, 'client/webpack-stats.json'), - } -} - -INSTALLED_APPS = ( - 'core', - 'bank', - 'djcelery', - 'gather', - 'modernomad', - 'api', - 'django_graphiql', - 'graphene_django', - 'graphapi', - 'django_behave', - 'bdd', - 'rest_framework', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.sites', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django.contrib.admin', - 'django.contrib.flatpages', - 'django.contrib.admindocs', - 'django.contrib.humanize', - 'webpack_loader', - 'compressor', - 'django_extensions', - 'django_filters', - # 'debug_toolbar', -) - -COMPRESS_PRECOMPILERS = ( - ('text/less', 'lessc {infile} {outfile}'), -) - -# FIXME: disabled. see https://github.com/embassynetwork/modernomad/issues/302 -# TEST_RUNNER = 'django_behave.runner.DjangoBehaveTestSuiteRunner' - -AUTH_PROFILE_MODULE = 'core.UserProfile' -ACCOUNT_ACTIVATION_DAYS = 7 # One week account activation window. - -# Discourse discussion group -DISCOURSE_BASE_URL = 'http://your-discourse-site.com' -DISCOURSE_SSO_SECRET = 'paste_your_secret_here' - -# If we add a page for the currently-logged-in user to view and edit -# their profile, we might want to use that here instead. -LOGIN_REDIRECT_URL = '/' -LOGIN_URL = '/people/login/' -LOGOUT_URL = '/people/logout/' - -# Celery configuration options -BROKER_URL = "amqp://guest:guest@localhost:5672//" -CELERY_TASK_SERIALIZER = 'json' -CELERY_RESULT_SERIALIZER = 'json' -CELERY_ENABLE_UTC = True -CELERY_ACCEPT_CONTENT = ['json', 'yaml'] - -REST_FRAMEWORK = { - 'DEFAULT_PERMISSION_CLASSES': [ - 'rest_framework.permissions.IsAuthenticated', - ] -} - -LIST_DOMAIN = "example.com" - -# import any local settings -try: - from .local_settings import * -except ImportError: - pass - - -NOSE_ARGS = [ - '--nocapture', - '--nologcapture' -] - - -class DisableMigrations(object): - def __contains__(self, item): - return True - - def __getitem__(self, item): - return None - - -TESTS_IN_PROGRESS = False -if 'test' in sys.argv[1:]: - PASSWORD_HASHERS = ( - 'django.contrib.auth.hashers.MD5PasswordHasher', - ) - TESTS_IN_PROGRESS = True - MIGRATION_MODULES = DisableMigrations() - -os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = "localhost:8000-8010,8080,9200-9300" diff --git a/modernomad/settings_docker.py b/modernomad/settings_docker.py deleted file mode 100644 index 2007e6fa..00000000 --- a/modernomad/settings_docker.py +++ /dev/null @@ -1,98 +0,0 @@ -# A sensible set of defaults for a production environment which can be -# overridden with environment variables - -import environ -from django.core.exceptions import ImproperlyConfigured -import os -from .settings import * - -env = environ.Env() - -# what mode are we running in? use this to trigger different settings. -DEVELOPMENT = 0 -PRODUCTION = 1 - -# default mode is production. change to dev as appropriate. -env_mode = env('MODE', default='PRODUCTION') -if env_mode == 'DEVELOPMENT': - MODE = DEVELOPMENT - DEBUG = True - EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' -elif env_mode == 'PRODUCTION': - MODE = PRODUCTION - DEBUG = False - - STATIC_ROOT = path("static_root/") - - # Collect static gathers client/dist/ into root of static/, - # which is why bundle dir is blank - WEBPACK_LOADER = { - 'DEFAULT': { - 'BUNDLE_DIR_NAME': '', - 'CACHE': True, - 'STATS_FILE': os.path.join(BASE_DIR, 'client/webpack-stats-prod.json'), - } - } - COMPRESS_OFFLINE = True -else: - raise ImproperlyConfigured('Unknown MODE setting') - -TEMPLATE_DEBUG = DEBUG - -SECRET_KEY = env('SECRET_KEY') -DATABASES = { - 'default': env.db('DATABASE_URL', default='postgres://postgres@postgres/postgres'), -} - -ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', default=[]) - -BROKER_URL = env('BROKER_URL', default=env('CLOUDAMQP_URL', default='amqp://guest:guest@rabbitmq//')) -CELERY_RESULT_BACKEND = BROKER_URL - -# this should be a TEST or PRODUCTION key depending on whether this is a local -# test/dev site or production! -STRIPE_SECRET_KEY = env('STRIPE_SECRET_KEY', default='') -STRIPE_PUBLISHABLE_KEY = env('STRIPE_PUBLISHABLE_KEY', default='') - -# Discourse discussion group -DISCOURSE_BASE_URL = env('DISCOURSE_BASE_URL', default='') -DISCOURSE_SSO_SECRET = env('DISCOURSE_SSO_SECRET', default='') - -ADMINS = (( - env('ADMIN_NAME', default='Unnamed'), - env('ADMIN_EMAIL', default='none@example.com') -),) - -MAILGUN_API_KEY = env('MAILGUN_API_KEY', default='') -LIST_DOMAIN = env('LIST_DOMAIN', default='somedomain.com') -EMAIL_SUBJECT_PREFIX = env('EMAIL_SUBJECT_PREFIX', default='[Modernomad] ') -DEFAULT_FROM_EMAIL = env('DEFAULT_FROM_EMAIL', default='stay@example.com') - -GOOGLE_ANALYTICS_PROPERTY_ID = env('GOOGLE_ANALYTICS_PROPERTY_ID', default='') -GOOGLE_ANALYTICS_DOMAIN = env('GOOGLE_ANALYTICS_DOMAIN', default='example.com') - -LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'handlers': { - 'console': { - 'class': 'logging.StreamHandler', - }, - }, - 'loggers': { - 'django': { - 'handlers': ['console'], - 'level': env('DJANGO_LOG_LEVEL', default='INFO'), - }, - }, -} - -# Media storage -AWS_STORAGE_BUCKET_NAME = env('AWS_STORAGE_BUCKET_NAME', default='') -AWS_ACCESS_KEY_ID = env('AWS_ACCESS_KEY_ID', default='') -AWS_SECRET_ACCESS_KEY = env('AWS_SECRET_ACCESS_KEY', default='') -AWS_DEFAULT_ACL = 'public-read' -if AWS_STORAGE_BUCKET_NAME: - DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage' - MEDIA_URL = env('MEDIA_URL', default='https://%s.s3.amazonaws.com/' % AWS_STORAGE_BUCKET_NAME) - From 32502e160f760c2e6ae64d308d429e22992edb9d Mon Sep 17 00:00:00 2001 From: Jonathan Sundqvist Date: Mon, 22 Oct 2018 19:21:11 +0200 Subject: [PATCH 20/29] Make sure that manage.py and wsgi run sane defaults --- .travis.yml | 3 ++- Dockerfile | 5 ++--- core/views/unsorted.py | 4 ++-- manage.py | 2 +- modernomad/settings/common.py | 8 +++++--- modernomad/settings/local.py | 1 + modernomad/urls/main.py | 2 +- modernomad/wsgi.py | 2 +- requirements.txt | 2 +- 9 files changed, 16 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index 986b66f0..1236f7a8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,8 @@ cache: - node_modules services: - postgresql +env: + - DJANGO_SETTINGS_MODULE=modernomad.settings.local install: - "pip install -U pip wheel" - "nvm install 8" @@ -15,7 +17,6 @@ install: before_script: - "cd client && npm install && cd .." - "cd client && ./node_modules/.bin/webpack --config webpack.prod.config.js && cd .." - - "cp modernomad/local_settings.travis.py modernomad/local_settings.py" script: ./manage.py test notifications: slack: diff --git a/Dockerfile b/Dockerfile index 4770dad9..0b484fa0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:2-alpine +FROM 3.6.6-alpine3.8 # So Pillow can find zlib ENV LIBRARY_PATH /lib:/usr/lib @@ -38,8 +38,7 @@ RUN cd client && node_modules/.bin/webpack --config webpack.prod.config.js # Set configuration last so we can change this without rebuilding the whole # image -ENV DJANGO_SETTINGS_MODULE modernomad.settings_docker -ENV MODE PRODUCTION +ENV DJANGO_SETTINGS_MODULE modernomad.settings.production # Number of gunicorn workers ENV WEB_CONCURRENCY 3 EXPOSE 8000 diff --git a/core/views/unsorted.py b/core/views/unsorted.py index 506cc638..d3eddd12 100644 --- a/core/views/unsorted.py +++ b/core/views/unsorted.py @@ -1561,7 +1561,7 @@ def BookingManageAction(request, location_slug, booking_id): days_until_arrival = (booking.use.arrive - datetime.date.today()).days if days_until_arrival <= location.welcome_email_days_ahead: guest_welcome(booking.use) - except CardError, e: + except CardError as e: # raise Booking.ResActionError(e) # messages.add_message(request, messages.INFO, "There was an error: %s" % e) # status_area_html = render(request, "snippets/res_status_area.html", {"r": booking, 'location': location, 'error': True}) @@ -1867,7 +1867,7 @@ def BillCharge(request, location_slug, bill_id): try: payment = payment_gateway.charge_user(user, bill, charge_amount_dollars, reference) - except CardError, e: + except CardError as e: messages.add_message(request, messages.INFO, "Charge failed with the following error: %s" % e) if bill.is_booking_bill(): return HttpResponseRedirect(reverse('booking_manage', args=(location_slug, bill.bookingbill.booking.id))) diff --git a/manage.py b/manage.py index 019020c7..a2c5617f 100755 --- a/manage.py +++ b/manage.py @@ -3,7 +3,7 @@ import sys if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "modernomad.settings") + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "modernomad.settings.local") from django.core.management import execute_from_command_line diff --git a/modernomad/settings/common.py b/modernomad/settings/common.py index f3181d4d..e82c01f4 100644 --- a/modernomad/settings/common.py +++ b/modernomad/settings/common.py @@ -22,7 +22,7 @@ TEMPLATE_DEBUG = False STRIPE_PUBLISHABLE_KEY = env('STRIPE_PUBLISHABLE_KEY', default='') -STRIPE_SECRET_KEY = env('STRIPE_SECRET_KEY', '') +STRIPE_SECRET_KEY = env('STRIPE_SECRET_KEY', default='') DATABASES = { 'default': env.db('DATABASE_URL', default='postgres://postgres@postgres/postgres'), @@ -71,7 +71,7 @@ # in apps' "static/" subdirectories and in STATICFILES_DIRS. # Example: "/home/media/media.lawrence.com/static/" STATIC_ROOT = BASE_DIR / 'static' -STATICFILES_DIRS = ('static', 'client/dist') +STATICFILES_DIRS = ('client/dist',) STATIC_URL = '/static/' STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.FileSystemFinder', @@ -152,6 +152,8 @@ } } + + INSTALLED_APPS = [ # django stuff 'django.contrib.auth', @@ -169,7 +171,7 @@ 'compressor', 'django_behave', 'django_extensions', - 'django_filters' + 'django_filters', 'django_graphiql', 'djcelery', 'graphene_django', diff --git a/modernomad/settings/local.py b/modernomad/settings/local.py index d62c50b7..93824e0f 100644 --- a/modernomad/settings/local.py +++ b/modernomad/settings/local.py @@ -6,6 +6,7 @@ INSTALLED_APPS = INSTALLED_APPS + [ 'debug_toolbar' ] +SECRET_KEY = 'local_development' EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' diff --git a/modernomad/urls/main.py b/modernomad/urls/main.py index 011f05ee..e4605782 100644 --- a/modernomad/urls/main.py +++ b/modernomad/urls/main.py @@ -1,7 +1,7 @@ from django.conf.urls import patterns, include, url from django.contrib import admin from modernomad.urls import user -from modernomad import settings +from django.conf import settings from django.views.generic import RedirectView from django.http import HttpResponse, HttpResponseRedirect from rest_framework import routers, serializers, viewsets diff --git a/modernomad/wsgi.py b/modernomad/wsgi.py index 5b76deea..60bda68b 100644 --- a/modernomad/wsgi.py +++ b/modernomad/wsgi.py @@ -15,7 +15,7 @@ """ import os -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "modernomad.settings") +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "modernomad.settings.production") # This application object is used by any WSGI server configured to use this # file. This includes Django's development server, if the WSGI_APPLICATION diff --git a/requirements.txt b/requirements.txt index c155c5ac..b173ca41 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ celery==3.1.20 django-behave django-celery==3.1.17 django-compressor==2.2 -django-debug-toolbar==1.4 +django-debug-toolbar==1.5 django-environ==0.4.3 django-extensions==2.1.3 django-filter==0.15.3 From e1f4312611715ff5fa762cdd90e282d2a296eb3f Mon Sep 17 00:00:00 2001 From: Ben Firshman Date: Tue, 23 Oct 2018 09:27:05 +0200 Subject: [PATCH 21/29] Use sqlite as default. Co-Authored-By: jonathan-s --- modernomad/settings/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modernomad/settings/common.py b/modernomad/settings/common.py index e82c01f4..95a23dff 100644 --- a/modernomad/settings/common.py +++ b/modernomad/settings/common.py @@ -25,7 +25,7 @@ STRIPE_SECRET_KEY = env('STRIPE_SECRET_KEY', default='') DATABASES = { - 'default': env.db('DATABASE_URL', default='postgres://postgres@postgres/postgres'), + 'default': env.db('DATABASE_URL', default='sqlite://modernomad.db'), } # Local time zone for this installation. Choices can be found here: From e280bedf58cd2a2ae94c014ed1154c66952911cd Mon Sep 17 00:00:00 2001 From: Jonathan Sundqvist Date: Tue, 23 Oct 2018 17:50:20 +0200 Subject: [PATCH 22/29] Fix up database settings --- .travis.yml | 2 ++ modernomad/settings/common.py | 14 +++----------- modernomad/settings/local.py | 11 ----------- 3 files changed, 5 insertions(+), 22 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1236f7a8..f20881a4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,11 +10,13 @@ services: - postgresql env: - DJANGO_SETTINGS_MODULE=modernomad.settings.local + DATABASE_URL=postgres://postgres@localhost/test_db install: - "pip install -U pip wheel" - "nvm install 8" - "pip install -r requirements.txt -r requirements.test.txt" before_script: + - psql -c 'create database test_db;' -U postgres - "cd client && npm install && cd .." - "cd client && ./node_modules/.bin/webpack --config webpack.prod.config.js && cd .." script: ./manage.py test diff --git a/modernomad/settings/common.py b/modernomad/settings/common.py index 95a23dff..083708d8 100644 --- a/modernomad/settings/common.py +++ b/modernomad/settings/common.py @@ -25,7 +25,7 @@ STRIPE_SECRET_KEY = env('STRIPE_SECRET_KEY', default='') DATABASES = { - 'default': env.db('DATABASE_URL', default='sqlite://modernomad.db'), + 'default': env.db('DATABASE_URL', default='sqlite:///modernomad.db'), } # Local time zone for this installation. Choices can be found here: @@ -152,8 +152,6 @@ } } - - INSTALLED_APPS = [ # django stuff 'django.contrib.auth', @@ -243,13 +241,7 @@ 'level': 'DEBUG', 'class': 'logging.FileHandler', 'filename': './django.log', - 'formatter': 'verbose', - }, - 'mail_admins': { - 'level': 'ERROR', - 'class': 'django.utils.log.AdminEmailHandler', - 'include_html': True, - 'formatter': 'verbose', + 'formatter': 'simple', } }, 'loggers': { @@ -259,7 +251,7 @@ 'propagate': True, }, 'django.request': { - 'handlers': ['file', 'mail_admins'], + 'handlers': ['file'], 'level': 'INFO', 'propagate': True, }, diff --git a/modernomad/settings/local.py b/modernomad/settings/local.py index 93824e0f..a29a7f40 100644 --- a/modernomad/settings/local.py +++ b/modernomad/settings/local.py @@ -9,14 +9,3 @@ SECRET_KEY = 'local_development' EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. - 'NAME': 'modernomad.db', # Or path to database file if using sqlite3. - 'USER': '', # Not used with sqlite3. - 'PASSWORD': '', # Not used with sqlite3. - 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. - 'PORT': '', # Set to empty string for default. Not used with sqlite3. - } -} From c407ecd172d14d8d481054da168a8149edd47687 Mon Sep 17 00:00:00 2001 From: Ben Firshman Date: Sun, 4 Nov 2018 08:24:32 +0100 Subject: [PATCH 23/29] Pull correct docker python Co-Authored-By: jonathan-s --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 0b484fa0..8af1d64a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM 3.6.6-alpine3.8 +FROM python:3.6.6-alpine3.8 # So Pillow can find zlib ENV LIBRARY_PATH /lib:/usr/lib From f80f9b95ad927011588cefd0f546d76c8104def0 Mon Sep 17 00:00:00 2001 From: Ben Firshman Date: Sun, 4 Nov 2018 08:26:11 +0100 Subject: [PATCH 24/29] Remove weird empty space Co-Authored-By: jonathan-s --- modernomad/settings/production.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modernomad/settings/production.py b/modernomad/settings/production.py index 884f087c..836f3b70 100644 --- a/modernomad/settings/production.py +++ b/modernomad/settings/production.py @@ -1,4 +1,4 @@ -from .common import * # noqa +from .common import * # noqa from .common import env from .common import BASE_DIR From 0c2567dbf7ad0858aa6ee52196776ea1f9e304c1 Mon Sep 17 00:00:00 2001 From: Ben Firshman Date: Sun, 4 Nov 2018 13:06:10 +0600 Subject: [PATCH 25/29] Add COMPRESS_OFFLINE back to production settings --- modernomad/settings/production.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modernomad/settings/production.py b/modernomad/settings/production.py index 836f3b70..028d107e 100644 --- a/modernomad/settings/production.py +++ b/modernomad/settings/production.py @@ -27,3 +27,4 @@ 'STATS_FILE': BASE_DIR / 'client/webpack-stats-prod.json', } } +COMPRESS_OFFLINE = True From 3b021ba2d9dfced088267f2822118296f3ba7241 Mon Sep 17 00:00:00 2001 From: Ben Firshman Date: Mon, 5 Nov 2018 13:22:08 +0600 Subject: [PATCH 26/29] Fix Docker development environment in new settings --- docker-compose.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index ba3ebd19..5e8479d2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,5 @@ -version: "3.0" +version: "3.4" + services: web: build: . @@ -6,8 +7,7 @@ services: ports: - "8000:8000" environment: - - "SECRET_KEY=insecure - only for development" - - "MODE=DEVELOPMENT" + - "DJANGO_SETTINGS_MODULE=modernomad.settings.local" - "STRIPE_SECRET_KEY" - "STRIPE_PUBLISHABLE_KEY" - "DISCOURSE_BASE_URL" @@ -32,8 +32,7 @@ services: build: . command: bin/celeryd environment: - - "SECRET_KEY=insecure - only for development" - - "MODE=DEVELOPMENT" + - "DJANGO_SETTINGS_MODULE=modernomad.settings.local" - "STRIPE_SECRET_KEY" - "STRIPE_PUBLISHABLE_KEY" - "DISCOURSE_BASE_URL" From 7c1cb0fc30323b19f81949fbc0bd2e2cf268c512 Mon Sep 17 00:00:00 2001 From: Ben Firshman Date: Mon, 5 Nov 2018 13:22:21 +0600 Subject: [PATCH 27/29] Fix Celery settings for Heroku --- modernomad/settings/common.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/modernomad/settings/common.py b/modernomad/settings/common.py index 083708d8..068418d7 100644 --- a/modernomad/settings/common.py +++ b/modernomad/settings/common.py @@ -211,7 +211,18 @@ CELERY_RESULT_SERIALIZER = 'json' CELERY_ENABLE_UTC = True CELERY_ACCEPT_CONTENT = ['json', 'yaml'] -CELERY_RESULT_BACKEND = BROKER_URL + +# Disabled when moving to Heroku for simplicity's sake, because no tasks +# have results. If results are needed, a suitable one can be picked for +# Heroku + CloudAMPQ. (Probably the "rpc" one, perhaps Django ORM?) +CELERY_RESULT_BACKEND = None + +# as per https://www.cloudamqp.com/docs/celery.html +BROKER_POOL_LIMIT = 1 +BROKER_HEARTBEAT = None +BROKER_CONNECTION_TIMEOUT = 30 +CELERY_EVENT_QUEUE_EXPIRES = 60 +CELERYD_PREFETCH_MULTIPLIER = 1 REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': [ From f7c77ac916ac7b0bca49b5eb6d5a250c824aafed Mon Sep 17 00:00:00 2001 From: Ben Firshman Date: Mon, 5 Nov 2018 13:34:12 +0600 Subject: [PATCH 28/29] Add app.json --- app.json | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 app.json diff --git a/app.json b/app.json new file mode 100644 index 00000000..42955c09 --- /dev/null +++ b/app.json @@ -0,0 +1,31 @@ +{ + "name": "modernomad", + "scripts": { + "postdeploy": "./manage.py generate_test_data" + }, + "env": { + "ALLOWED_HOSTS": ".herokuapp.com", + "AWS_ACCESS_KEY_ID": { + "required": true + }, + "AWS_SECRET_ACCESS_KEY": { + "required": true + }, + "AWS_STORAGE_BUCKET_NAME": { + "required": true + }, + "SECRET_KEY": { + "generator": "secret" + } + }, + "formation": { + "worker": { + "quantity": 1 + }, + "web": { + "quantity": 1 + } + }, + "addons": ["cloudamqp", "papertrail", "heroku-postgresql"], + "buildpacks": [] +} From 7c828586edec50b98113407618bd3ef84f97d108 Mon Sep 17 00:00:00 2001 From: Ben Firshman Date: Mon, 5 Nov 2018 13:49:57 +0600 Subject: [PATCH 29/29] just a test --- app.json | 1 - templates/index.html | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app.json b/app.json index 42955c09..eb6c8e14 100644 --- a/app.json +++ b/app.json @@ -1,7 +1,6 @@ { "name": "modernomad", "scripts": { - "postdeploy": "./manage.py generate_test_data" }, "env": { "ALLOWED_HOSTS": ".herokuapp.com", diff --git a/templates/index.html b/templates/index.html index 9a08db57..308ef4f3 100644 --- a/templates/index.html +++ b/templates/index.html @@ -7,7 +7,7 @@

- Embassy Network + ʞɹoʍʇǝN ʎssɐqɯƎ

A network of place-based communities experimenting with new forms of governance and solidarity.