├── .gitignore ├── LICENSE ├── README.md ├── requirements.txt ├── src ├── accounts │ ├── __init__.py │ ├── admin.py │ ├── forms.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_userprofile.py │ │ ├── 0003_membership.py │ │ ├── 0004_auto_20150203_0434.py │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py ├── analytics │ ├── __init__.py │ ├── admin.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_pageview_timestamp.py │ │ ├── 0003_auto_20150131_1934.py │ │ ├── 0004_auto_20150131_1936.py │ │ ├── 0005_auto_20150131_1946.py │ │ ├── 0006_auto_20150131_2022.py │ │ ├── 0007_auto_20150203_0328.py │ │ ├── 0008_auto_20150203_0434.py │ │ ├── 0009_auto_20150203_1909.py │ │ ├── 0010_auto_20150203_1922.py │ │ ├── 0011_auto_20150204_2042.py │ │ ├── 0012_auto_20150206_2201.py │ │ ├── 0013_auto_20150206_2230.py │ │ ├── 0014_auto_20150206_2232.py │ │ ├── 0015_auto_20150207_0140.py │ │ ├── 0016_auto_20150207_0206.py │ │ └── __init__.py │ ├── models.py │ ├── signals.py │ ├── tests.py │ └── views.py ├── billing │ ├── __init__.py │ ├── admin.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20150203_1909.py │ │ ├── 0003_auto_20150203_1922.py │ │ ├── 0004_auto_20150204_2042.py │ │ ├── 0005_auto_20150206_2201.py │ │ ├── 0006_auto_20150206_2230.py │ │ ├── 0007_auto_20150206_2232.py │ │ ├── 0008_auto_20150207_0140.py │ │ ├── 0009_auto_20150207_0206.py │ │ └── __init__.py │ ├── models.py │ ├── signals.py │ ├── tests.py │ ├── utils.py │ └── views.py ├── comments │ ├── __init__.py │ ├── admin.py │ ├── forms.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_comment_parent.py │ │ ├── 0003_auto_20150125_0038.py │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py ├── db.sqlite3 ├── manage.py ├── notifications │ ├── __init__.py │ ├── admin.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20150126_1924.py │ │ ├── 0003_auto_20150126_2134.py │ │ ├── 0004_auto_20150126_2212.py │ │ ├── 0005_remove_notification_unread.py │ │ └── __init__.py │ ├── models.py │ ├── signals.py │ ├── tests.py │ └── views.py ├── srvup │ ├── __init__.py │ ├── __init__.pyc │ ├── forms.py │ ├── forms.pyc │ ├── settings.py │ ├── settings.pyc │ ├── urls.py │ ├── urls.pyc │ ├── views.py │ ├── views.pyc │ ├── wsgi.py │ └── wsgi.pyc ├── templates │ ├── accounts │ │ ├── account_home.html │ │ ├── account_login_register.html │ │ ├── home_logged_in.html │ │ └── home_visitor.html │ ├── base.html │ ├── billing │ │ ├── history.html │ │ └── upgrade.html │ ├── comments │ │ └── comment_thread.html │ ├── company │ │ └── contact_us.html │ ├── extra_head_info.html │ ├── footer.html │ ├── form.html │ ├── home_visitor.html │ ├── javascript.html │ ├── navbar.html │ ├── notifications │ │ └── all.html │ └── videos │ │ ├── category_detail.html │ │ ├── category_list.html │ │ ├── video_detail.html │ │ └── video_list.html └── videos │ ├── __init__.py │ ├── __init__.pyc │ ├── admin.py │ ├── admin.pyc │ ├── forms.py │ ├── migrations │ ├── 0001_initial.py │ ├── 0001_initial.pyc │ ├── 0002_auto_20150116_2215.py │ ├── 0003_auto_20150117_0006.py │ ├── 0004_auto_20150117_0013.py │ ├── 0005_auto_20150117_0021.py │ ├── 0006_category_slug.py │ ├── 0007_auto_20150117_0048.py │ ├── 0008_auto_20150119_2127.py │ ├── 0009_video_share_message.py │ ├── 0010_auto_20150120_2335.py │ ├── 0011_taggeditem.py │ ├── 0012_auto_20150126_0712.py │ ├── 0013_auto_20150130_2131.py │ ├── 0014_auto_20150131_1915.py │ ├── __init__.py │ └── __init__.pyc │ ├── models.py │ ├── models.pyc │ ├── tests.py │ ├── utils.py │ └── views.py ├── srvup.sublime-project ├── srvup.sublime-workspace └── static ├── media └── images │ ├── Screen_Shot_2015-01-30_at_1.07.12_PM.png │ ├── djangogap.png │ ├── heroku_django.png │ ├── launch_with_code.png │ ├── open-ecommerce.png │ ├── srvup_membership.png │ └── try_django_17.jpg ├── static_dirs └── js │ ├── ie-emulation-modes-warning.js │ └── ie10-viewport-bug-workaround.js └── static_root ├── admin ├── css │ ├── base.css │ ├── changelists.css │ ├── dashboard.css │ ├── forms.css │ ├── ie.css │ ├── login.css │ ├── rtl.css │ └── widgets.css ├── img │ ├── changelist-bg.gif │ ├── changelist-bg_rtl.gif │ ├── default-bg-reverse.gif │ ├── default-bg.gif │ ├── deleted-overlay.gif │ ├── gis │ │ ├── move_vertex_off.png │ │ └── move_vertex_on.png │ ├── icon-no.gif │ ├── icon-unknown.gif │ ├── icon-yes.gif │ ├── icon_addlink.gif │ ├── icon_alert.gif │ ├── icon_calendar.gif │ ├── icon_changelink.gif │ ├── icon_clock.gif │ ├── icon_deletelink.gif │ ├── icon_error.gif │ ├── icon_searchbox.png │ ├── icon_success.gif │ ├── inline-delete-8bit.png │ ├── inline-delete.png │ ├── inline-restore-8bit.png │ ├── inline-restore.png │ ├── inline-splitter-bg.gif │ ├── nav-bg-grabber.gif │ ├── nav-bg-reverse.gif │ ├── nav-bg-selected.gif │ ├── nav-bg.gif │ ├── selector-icons.gif │ ├── selector-search.gif │ ├── sorting-icons.gif │ ├── tooltag-add.png │ └── tooltag-arrowright.png └── js │ ├── LICENSE-JQUERY.txt │ ├── SelectBox.js │ ├── SelectFilter2.js │ ├── actions.js │ ├── actions.min.js │ ├── admin │ ├── DateTimeShortcuts.js │ └── RelatedObjectLookups.js │ ├── calendar.js │ ├── collapse.js │ ├── collapse.min.js │ ├── core.js │ ├── inlines.js │ ├── inlines.min.js │ ├── jquery.init.js │ ├── jquery.js │ ├── jquery.min.js │ ├── prepopulate.js │ ├── prepopulate.min.js │ ├── timeparse.js │ └── urlify.js └── js ├── ie-emulation-modes-warning.js └── ie10-viewport-bug-workaround.js /.gitignore: -------------------------------------------------------------------------------- 1 | git_log.txt 2 | .DS_Store 3 | .Python 4 | bin/ 5 | include/ 6 | lib/ 7 | 8 | # Byte-compiled / optimized / DLL files 9 | __pycache__/ 10 | *.py[cod] 11 | 12 | # C extensions 13 | *.so 14 | 15 | # Distribution / packaging 16 | .Python 17 | env/ 18 | build/ 19 | develop-eggs/ 20 | dist/ 21 | downloads/ 22 | eggs/ 23 | lib/ 24 | lib64/ 25 | parts/ 26 | sdist/ 27 | var/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .coverage 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | 57 | # Sphinx documentation 58 | docs/_build/ 59 | 60 | # PyBuilder 61 | target/ 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Coding For Entrepreneurs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.7.2 2 | Pillow==2.7.0 3 | PyJWT==0.4.1 4 | braintree==3.10.0 5 | django-crispy-forms==1.4.0 6 | oauthlib==0.7.2 7 | python-openid==2.2.5 8 | python-social-auth==0.2.1 9 | requests==2.5.1 10 | requests-oauthlib==0.4.2 11 | six==1.9.0 12 | wsgiref==0.1.2 13 | -------------------------------------------------------------------------------- /src/accounts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/src/accounts/__init__.py -------------------------------------------------------------------------------- /src/accounts/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.auth.models import Group 3 | from django.contrib.auth.admin import UserAdmin 4 | from django.contrib.auth.forms import ReadOnlyPasswordHashField 5 | 6 | from .models import MyUser, UserProfile 7 | from .forms import UserChangeForm, UserCreationForm 8 | 9 | class MyUserAdmin(UserAdmin): 10 | # The forms to add and change user instances 11 | form = UserChangeForm 12 | add_form = UserCreationForm 13 | 14 | # The fields to be used in displaying the User model. 15 | # These override the definitions on the base UserAdmin 16 | # that reference specific fields on auth.User. 17 | list_display = ( 'username', 'email', 'is_admin', 'is_member') 18 | list_filter = ('is_admin', 'is_member',) 19 | fieldsets = ( 20 | (None, {'fields': ('email', 'password')}), 21 | ('Personal info', {'fields': ('first_name','last_name')}), 22 | ('Permissions', {'fields': ('is_admin', 'is_member')}), 23 | ) 24 | # add_fieldsets is not a standard ModelAdmin attribute. UserAdmin 25 | # overrides get_fieldsets to use this attribute when creating a user. 26 | add_fieldsets = ( 27 | (None, { 28 | 'classes': ('wide',), 29 | 'fields': ( 'username', 'email', 'password1', 'password2', 'first_name', 'last_name',)} 30 | ), 31 | ) 32 | search_fields = ('email', 'username', 'first_name', 'last_name',) 33 | ordering = ('username',) 34 | filter_horizontal = () 35 | 36 | # Now register the new UserAdmin... 37 | admin.site.register(MyUser, MyUserAdmin) 38 | 39 | admin.site.register(UserProfile) 40 | # ... and, since we're not using Django's built-in permissions, 41 | # unregister the Group model from admin. 42 | admin.site.unregister(Group) -------------------------------------------------------------------------------- /src/accounts/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.contrib.auth.forms import ReadOnlyPasswordHashField 3 | 4 | from .models import MyUser 5 | 6 | 7 | class RegisterForm(forms.Form): 8 | username = forms.CharField() 9 | email = forms.EmailField() 10 | password1 = forms.CharField(label='Password', widget=forms.PasswordInput) 11 | password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput) 12 | 13 | def clean_password2(self): 14 | # Check that the two password entries match 15 | password1 = self.cleaned_data.get("password1") 16 | if len(password1) <= 4: 17 | raise forms.ValidationError("Password is too short") 18 | password2 = self.cleaned_data.get("password2") 19 | if password1 and password2 and password1 != password2: 20 | raise forms.ValidationError("Passwords don't match") 21 | return password2 22 | 23 | def clean_username(self): 24 | username = self.cleaned_data.get("username") 25 | try: 26 | exists = MyUser.objects.get(username=username) 27 | raise forms.ValidationError("This username is taken") 28 | except MyUser.DoesNotExist: 29 | return username 30 | except: 31 | raise forms.ValidationError("There was an error, please try again or contact us.") 32 | 33 | 34 | def clean_email(self): 35 | email = self.cleaned_data.get("email") 36 | try: 37 | exists = MyUser.objects.get(email=email) 38 | raise forms.ValidationError("This username is taken") 39 | except MyUser.DoesNotExist: 40 | return email 41 | except: 42 | raise forms.ValidationError("There was an error, please try again or contact us.") 43 | 44 | 45 | 46 | 47 | class UserCreationForm(forms.ModelForm): 48 | """A form for creating new users. Includes all the required 49 | fields, plus a repeated password.""" 50 | password1 = forms.CharField(label='Password', widget=forms.PasswordInput) 51 | password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput) 52 | 53 | class Meta: 54 | model = MyUser 55 | fields = ('email', 'username', 'first_name', 'last_name') 56 | 57 | def clean_password2(self): 58 | # Check that the two password entries match 59 | password1 = self.cleaned_data.get("password1") 60 | password2 = self.cleaned_data.get("password2") 61 | if password1 and password2 and password1 != password2: 62 | raise forms.ValidationError("Passwords don't match") 63 | return password2 64 | 65 | def save(self, commit=True): 66 | # Save the provided password in hashed format 67 | user = super(UserCreationForm, self).save(commit=False) 68 | user.set_password(self.cleaned_data["password1"]) 69 | if commit: 70 | user.save() 71 | return user 72 | 73 | 74 | class UserChangeForm(forms.ModelForm): 75 | """A form for updating users. Includes all the fields on 76 | the user, but replaces the password field with admin's 77 | password hash display field. 78 | """ 79 | password = ReadOnlyPasswordHashField() 80 | 81 | class Meta: 82 | model = MyUser 83 | fields = ('email', 'password', 'username', 'first_name', 'last_name', 'is_active', 'is_admin', "is_member") 84 | 85 | def clean_password(self): 86 | # Regardless of what the user provides, return the initial value. 87 | # This is done here, rather than on the field, because the 88 | # field does not have access to the initial value 89 | return self.initial["password"] 90 | 91 | 92 | class LoginForm(forms.Form): 93 | username = forms.CharField(label="Username") 94 | password = forms.CharField(widget=forms.PasswordInput()) 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /src/accounts/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import django.utils.timezone 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='MyUser', 16 | fields=[ 17 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 18 | ('password', models.CharField(max_length=128, verbose_name='password')), 19 | ('last_login', models.DateTimeField(default=django.utils.timezone.now, verbose_name='last login')), 20 | ('username', models.CharField(unique=True, max_length=255)), 21 | ('email', models.EmailField(unique=True, max_length=255, verbose_name=b'email address')), 22 | ('first_name', models.CharField(max_length=120, null=True, blank=True)), 23 | ('last_name', models.CharField(max_length=120, null=True, blank=True)), 24 | ('is_member', models.BooleanField(default=False, verbose_name=b'Is Paid Member')), 25 | ('is_active', models.BooleanField(default=True)), 26 | ('is_admin', models.BooleanField(default=False)), 27 | ], 28 | options={ 29 | 'abstract': False, 30 | }, 31 | bases=(models.Model,), 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /src/accounts/migrations/0002_userprofile.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | from django.conf import settings 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('accounts', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='UserProfile', 17 | fields=[ 18 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 19 | ('bio', models.TextField(null=True, blank=True)), 20 | ('facebook_link', models.CharField(max_length=320, null=True, verbose_name=b'Facebook profile url', blank=True)), 21 | ('twitter_handle', models.CharField(max_length=320, null=True, verbose_name=b'Twitter handle', blank=True)), 22 | ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL)), 23 | ], 24 | options={ 25 | }, 26 | bases=(models.Model,), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /src/accounts/migrations/0003_membership.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import datetime 6 | from django.conf import settings 7 | from django.utils.timezone import utc 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('accounts', '0002_userprofile'), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Membership', 19 | fields=[ 20 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 21 | ('date_end', models.DateTimeField(default=datetime.datetime(2015, 2, 3, 3, 28, 16, 186674, tzinfo=utc))), 22 | ('date_start', models.DateTimeField(default=datetime.datetime(2015, 2, 3, 3, 28, 16, 186899, tzinfo=utc))), 23 | ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL)), 24 | ], 25 | options={ 26 | }, 27 | bases=(models.Model,), 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /src/accounts/migrations/0004_auto_20150203_0434.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('accounts', '0003_membership'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='membership', 16 | name='user', 17 | ), 18 | migrations.DeleteModel( 19 | name='Membership', 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /src/accounts/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/src/accounts/migrations/__init__.py -------------------------------------------------------------------------------- /src/accounts/models.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.contrib.auth.models import BaseUserManager, AbstractBaseUser 3 | from django.contrib.auth.signals import user_logged_in 4 | from django.db import models 5 | from django.db.models.signals import post_save 6 | from django.utils import timezone 7 | 8 | 9 | import braintree 10 | 11 | braintree.Configuration.configure(braintree.Environment.Sandbox, 12 | merchant_id=settings.BRAINTREE_MERCHANT_ID, 13 | public_key=settings.BRAINTREE_PUBLIC_KEY, 14 | private_key=settings.BRAINTREE_PRIVATE_KEY) 15 | 16 | 17 | 18 | 19 | from billing.models import Membership, UserMerchantId 20 | from notifications.signals import notify 21 | 22 | class MyUserManager(BaseUserManager): 23 | def create_user(self, username=None, email=None, password=None): 24 | """ 25 | Creates and saves a User with the given username, email and password. 26 | """ 27 | if not username: 28 | raise ValueError('Must include username') 29 | 30 | if not email: 31 | raise ValueError('Users must have an email address') 32 | 33 | user = self.model( 34 | username = username, 35 | email = self.normalize_email(email), 36 | ) 37 | 38 | user.set_password(password) 39 | user.save(using=self._db) 40 | return user 41 | 42 | def create_superuser(self, username, email, password): 43 | """ 44 | Creates and saves a superuser with the given username, email and password. 45 | """ 46 | 47 | user = self.create_user( 48 | username=username, 49 | email=email, 50 | password=password 51 | ) 52 | user.is_admin = True 53 | user.save(using=self._db) 54 | return user 55 | 56 | 57 | class MyUser(AbstractBaseUser): 58 | username = models.CharField( 59 | max_length=255, 60 | unique=True, 61 | ) 62 | email = models.EmailField( 63 | verbose_name='email address', 64 | max_length=255, 65 | unique=True, 66 | ) 67 | first_name = models.CharField( 68 | max_length=120, 69 | null=True, 70 | blank=True, 71 | ) 72 | last_name = models.CharField( 73 | max_length=120, 74 | null=True, 75 | blank=True, 76 | ) 77 | is_member = models.BooleanField(default=False, 78 | verbose_name='Is Paid Member') 79 | is_active = models.BooleanField(default=True) 80 | is_admin = models.BooleanField(default=False) 81 | 82 | objects = MyUserManager() 83 | 84 | USERNAME_FIELD = 'username' 85 | REQUIRED_FIELDS = ['email'] 86 | 87 | def get_full_name(self): 88 | # The user is identified by their email address 89 | return "%s %s" %(self.first_name, self.last_name) 90 | 91 | def get_short_name(self): 92 | # The user is identified by their email address 93 | return self.first_name 94 | 95 | def __unicode__(self): 96 | return self.username 97 | 98 | def has_perm(self, perm, obj=None): 99 | "Does the user have a specific permission?" 100 | # Simplest possible answer: Yes, always 101 | return True 102 | 103 | def has_module_perms(self, app_label): 104 | "Does the user have permissions to view the app `app_label`?" 105 | # Simplest possible answer: Yes, always 106 | return True 107 | 108 | @property 109 | def is_staff(self): 110 | "Is the user a member of staff?" 111 | # Simplest possible answer: All admins are staff 112 | return self.is_admin 113 | 114 | 115 | 116 | 117 | 118 | def user_logged_in_signal(sender, signal, request, user, **kwargs): 119 | request.session.set_expiry(60000) 120 | membership_obj, created = Membership.objects.get_or_create(user=user) 121 | if created: 122 | membership_obj.date_start = timezone.now() 123 | membership_obj.save() 124 | user.is_member = True 125 | user.save() 126 | user.membership.update_status() 127 | 128 | 129 | user_logged_in.connect(user_logged_in_signal) 130 | 131 | 132 | 133 | 134 | class UserProfile(models.Model): 135 | user = models.OneToOneField(MyUser) 136 | bio = models.TextField(null=True, blank=True) 137 | facebook_link = models.CharField(max_length=320, 138 | null=True, 139 | blank=True, 140 | verbose_name='Facebook profile url') 141 | twitter_handle = models.CharField(max_length=320, 142 | null=True, 143 | blank=True, 144 | verbose_name='Twitter handle') 145 | 146 | 147 | def __unicode__(self): 148 | return self.user.username 149 | 150 | 151 | 152 | def new_user_receiver(sender, instance, created, *args, **kwargs): 153 | if created: 154 | new_profile, is_created = UserProfile.objects.get_or_create(user=instance) 155 | #print new_profile, is_created 156 | notify.send(instance, 157 | recipient=MyUser.objects.get(username='jmitchel3'), #admin user 158 | verb='New user created.') 159 | # merchant account customer id -- stripe vs braintree 160 | try: 161 | merchant_obj = UserMerchantId.objects.get(user=instance) 162 | except: 163 | new_customer_result = braintree.Customer.create({ 164 | "email": instance.email 165 | }) 166 | if new_customer_result.is_success: 167 | merchant_obj, created = UserMerchantId.objects.get_or_create(user=instance) 168 | merchant_obj.customer_id = new_customer_result.customer.id 169 | merchant_obj.save() 170 | print """Customer created with id = {0}""".format(new_customer_result.customer.id) 171 | else: 172 | print "Error: {0}".format(new_customer_result.message) 173 | messages.error(request, "There was an error with your account. Please contact us.") 174 | 175 | 176 | # send email for verifying user email 177 | 178 | post_save.connect(new_user_receiver, sender=MyUser) 179 | 180 | 181 | -------------------------------------------------------------------------------- /src/accounts/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /src/accounts/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import authenticate, login, logout 2 | from django.contrib.auth.decorators import login_required 3 | from django.shortcuts import render, HttpResponseRedirect, redirect 4 | from django.utils.safestring import mark_safe 5 | from django.core.urlresolvers import reverse 6 | 7 | 8 | # Create your views here. 9 | from billing.models import Transaction 10 | from notifications.models import Notification 11 | 12 | from .forms import LoginForm, RegisterForm 13 | from .models import MyUser 14 | 15 | @login_required 16 | def account_home(request): 17 | notifications = Notification.objects.get_recent_for_user(request.user, 6) 18 | transactions = Transaction.objects.get_recent_for_user(request.user, 3) 19 | context = { 20 | "notifications": notifications, 21 | "transactions": transactions 22 | } 23 | 24 | return render(request, "accounts/account_home.html", context) 25 | 26 | 27 | 28 | def auth_logout(request): 29 | logout(request) 30 | return HttpResponseRedirect('/') 31 | 32 | 33 | 34 | def auth_login(request): 35 | form = LoginForm(request.POST or None) 36 | next_url = request.GET.get('next') 37 | if form.is_valid(): 38 | username = form.cleaned_data['username'] 39 | password = form.cleaned_data['password'] 40 | print username, password 41 | user = authenticate(username=username, password=password) 42 | if user is not None: 43 | login(request, user) 44 | if next_url is not None: 45 | return HttpResponseRedirect(next_url) 46 | return HttpResponseRedirect("/") 47 | action_url = reverse("login") 48 | title = "Login" 49 | submit_btn = title 50 | submit_btn_class = "btn-success btn-block" 51 | extra_form_link = "Upgrade your account today here!" %(reverse("account_upgrade")) 52 | context = { 53 | "form": form, 54 | "action_url": action_url, 55 | "title": title, 56 | "submit_btn": submit_btn, 57 | "submit_btn_class": submit_btn_class, 58 | "extra_form_link":extra_form_link 59 | } 60 | return render(request, "accounts/account_login_register.html", context) 61 | 62 | 63 | def auth_register(request): 64 | 65 | form = RegisterForm(request.POST or None) 66 | if form.is_valid(): 67 | username = form.cleaned_data['username'] 68 | email = form.cleaned_data['email'] 69 | password = form.cleaned_data['password2'] 70 | #MyUser.objects.create_user(username=username, email=email, password=password) 71 | new_user = MyUser() 72 | new_user.username = username 73 | new_user.email = email 74 | #new_user.password = password #WRONG 75 | new_user.set_password(password) #RIGHT 76 | new_user.save() 77 | 78 | action_url = reverse("register") 79 | title = "Register" 80 | submit_btn = "Create free account" 81 | 82 | context = { 83 | "form": form, 84 | "action_url": action_url, 85 | "title": title, 86 | "submit_btn": submit_btn 87 | } 88 | return render(request, "accounts/account_login_register.html", context) 89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /src/analytics/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/src/analytics/__init__.py -------------------------------------------------------------------------------- /src/analytics/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | 5 | from .models import PageView 6 | 7 | admin.site.register(PageView) -------------------------------------------------------------------------------- /src/analytics/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | from django.conf import settings 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='PageView', 17 | fields=[ 18 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 19 | ('path', models.CharField(max_length=350)), 20 | ('count', models.PositiveIntegerField(default=1)), 21 | ('user', models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, null=True)), 22 | ], 23 | options={ 24 | }, 25 | bases=(models.Model,), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /src/analytics/migrations/0002_pageview_timestamp.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import datetime 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('analytics', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='pageview', 17 | name='timestamp', 18 | field=models.DateField(default=datetime.date.today), 19 | preserve_default=True, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /src/analytics/migrations/0003_auto_20150131_1934.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import datetime 6 | from django.utils.timezone import utc 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('analytics', '0002_pageview_timestamp'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='pageview', 18 | name='timestamp', 19 | field=models.DateField(default=datetime.datetime(2015, 1, 31, 19, 34, 58, 891630, tzinfo=utc)), 20 | preserve_default=True, 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /src/analytics/migrations/0004_auto_20150131_1936.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import datetime 6 | from django.utils.timezone import utc 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('analytics', '0003_auto_20150131_1934'), 13 | ] 14 | 15 | operations = [ 16 | migrations.RemoveField( 17 | model_name='pageview', 18 | name='count', 19 | ), 20 | migrations.AlterField( 21 | model_name='pageview', 22 | name='timestamp', 23 | field=models.DateTimeField(default=datetime.datetime(2015, 1, 31, 19, 36, 32, 249822, tzinfo=utc)), 24 | preserve_default=True, 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /src/analytics/migrations/0005_auto_20150131_1946.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import datetime 6 | from django.utils.timezone import utc 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('contenttypes', '0001_initial'), 13 | ('analytics', '0004_auto_20150131_1936'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AddField( 18 | model_name='pageview', 19 | name='primary_content_type', 20 | field=models.ForeignKey(related_name='primary_obj', blank=True, to='contenttypes.ContentType', null=True), 21 | preserve_default=True, 22 | ), 23 | migrations.AddField( 24 | model_name='pageview', 25 | name='primary_object_id', 26 | field=models.PositiveIntegerField(null=True, blank=True), 27 | preserve_default=True, 28 | ), 29 | migrations.AddField( 30 | model_name='pageview', 31 | name='secondary_content_type', 32 | field=models.ForeignKey(related_name='secondary_obj', blank=True, to='contenttypes.ContentType', null=True), 33 | preserve_default=True, 34 | ), 35 | migrations.AddField( 36 | model_name='pageview', 37 | name='secondary_object_id', 38 | field=models.PositiveIntegerField(null=True, blank=True), 39 | preserve_default=True, 40 | ), 41 | migrations.AlterField( 42 | model_name='pageview', 43 | name='timestamp', 44 | field=models.DateTimeField(default=datetime.datetime(2015, 1, 31, 19, 46, 7, 655003, tzinfo=utc)), 45 | preserve_default=True, 46 | ), 47 | ] 48 | -------------------------------------------------------------------------------- /src/analytics/migrations/0006_auto_20150131_2022.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import datetime 6 | from django.utils.timezone import utc 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('analytics', '0005_auto_20150131_1946'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterModelOptions( 17 | name='pageview', 18 | options={'ordering': ['-timestamp']}, 19 | ), 20 | migrations.AlterField( 21 | model_name='pageview', 22 | name='timestamp', 23 | field=models.DateTimeField(default=datetime.datetime(2015, 1, 31, 20, 22, 18, 666845, tzinfo=utc)), 24 | preserve_default=True, 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /src/analytics/migrations/0007_auto_20150203_0328.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import datetime 6 | from django.utils.timezone import utc 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('analytics', '0006_auto_20150131_2022'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='pageview', 18 | name='timestamp', 19 | field=models.DateTimeField(default=datetime.datetime(2015, 2, 3, 3, 28, 16, 193353, tzinfo=utc)), 20 | preserve_default=True, 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /src/analytics/migrations/0008_auto_20150203_0434.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import datetime 6 | from django.utils.timezone import utc 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('analytics', '0007_auto_20150203_0328'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='pageview', 18 | name='timestamp', 19 | field=models.DateTimeField(default=datetime.datetime(2015, 2, 3, 4, 34, 30, 741066, tzinfo=utc)), 20 | preserve_default=True, 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /src/analytics/migrations/0009_auto_20150203_1909.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import datetime 6 | from django.utils.timezone import utc 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('analytics', '0008_auto_20150203_0434'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='pageview', 18 | name='timestamp', 19 | field=models.DateTimeField(default=datetime.datetime(2015, 2, 3, 19, 9, 28, 621255, tzinfo=utc)), 20 | preserve_default=True, 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /src/analytics/migrations/0010_auto_20150203_1922.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import datetime 6 | from django.utils.timezone import utc 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('analytics', '0009_auto_20150203_1909'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='pageview', 18 | name='timestamp', 19 | field=models.DateTimeField(default=datetime.datetime(2015, 2, 3, 19, 22, 21, 464395, tzinfo=utc)), 20 | preserve_default=True, 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /src/analytics/migrations/0011_auto_20150204_2042.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import datetime 6 | from django.utils.timezone import utc 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('analytics', '0010_auto_20150203_1922'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='pageview', 18 | name='timestamp', 19 | field=models.DateTimeField(default=datetime.datetime(2015, 2, 4, 20, 42, 27, 846790, tzinfo=utc)), 20 | preserve_default=True, 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /src/analytics/migrations/0012_auto_20150206_2201.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import datetime 6 | from django.utils.timezone import utc 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('analytics', '0011_auto_20150204_2042'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='pageview', 18 | name='timestamp', 19 | field=models.DateTimeField(default=datetime.datetime(2015, 2, 6, 22, 1, 49, 540439, tzinfo=utc)), 20 | preserve_default=True, 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /src/analytics/migrations/0013_auto_20150206_2230.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import datetime 6 | from django.utils.timezone import utc 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('analytics', '0012_auto_20150206_2201'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='pageview', 18 | name='timestamp', 19 | field=models.DateTimeField(default=datetime.datetime(2015, 2, 6, 22, 30, 26, 415492, tzinfo=utc)), 20 | preserve_default=True, 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /src/analytics/migrations/0014_auto_20150206_2232.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import datetime 6 | from django.utils.timezone import utc 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('analytics', '0013_auto_20150206_2230'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='pageview', 18 | name='timestamp', 19 | field=models.DateTimeField(default=datetime.datetime(2015, 2, 6, 22, 32, 49, 710382, tzinfo=utc)), 20 | preserve_default=True, 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /src/analytics/migrations/0015_auto_20150207_0140.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import datetime 6 | from django.utils.timezone import utc 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('analytics', '0014_auto_20150206_2232'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='pageview', 18 | name='timestamp', 19 | field=models.DateTimeField(default=datetime.datetime(2015, 2, 7, 1, 40, 27, 332639, tzinfo=utc)), 20 | preserve_default=True, 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /src/analytics/migrations/0016_auto_20150207_0206.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import datetime 6 | from django.utils.timezone import utc 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('analytics', '0015_auto_20150207_0140'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='pageview', 18 | name='timestamp', 19 | field=models.DateTimeField(default=datetime.datetime(2015, 2, 7, 2, 6, 28, 708564, tzinfo=utc)), 20 | preserve_default=True, 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /src/analytics/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/src/analytics/migrations/__init__.py -------------------------------------------------------------------------------- /src/analytics/models.py: -------------------------------------------------------------------------------- 1 | 2 | from django.conf import settings 3 | from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation 4 | from django.contrib.contenttypes.models import ContentType 5 | from django.core.urlresolvers import reverse 6 | from django.db import models 7 | from django.utils import timezone 8 | 9 | from videos.models import Video, Category 10 | from .signals import page_view 11 | 12 | 13 | class PageViewQuerySet(models.query.QuerySet): 14 | def videos(self): 15 | content_type = ContentType.objects.get_for_model(Video) 16 | return self.filter(primary_content_type=content_type) 17 | 18 | def categories(self): 19 | content_type = ContentType.objects.get_for_model(Category) 20 | return self.filter(primary_content_type=content_type) 21 | 22 | 23 | class PageViewManager(models.Manager): 24 | def get_queryset(self): 25 | return PageViewQuerySet(self.model, using=self._db) 26 | 27 | def get_videos(self): 28 | return self.get_queryset().videos() 29 | 30 | def get_categories(self): 31 | return self.get_queryset().categories() 32 | 33 | 34 | class PageView(models.Model): 35 | path = models.CharField(max_length=350) 36 | user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True) 37 | primary_content_type = models.ForeignKey(ContentType, related_name='primary_obj',\ 38 | null=True, blank=True) 39 | primary_object_id = models.PositiveIntegerField(null=True, blank=True) 40 | primary_object = GenericForeignKey("primary_content_type", "primary_object_id") 41 | 42 | secondary_content_type = models.ForeignKey(ContentType, related_name='secondary_obj',\ 43 | null=True, blank=True) 44 | secondary_object_id = models.PositiveIntegerField(null=True, blank=True) 45 | secondary_object = GenericForeignKey("secondary_content_type", "secondary_object_id") 46 | 47 | timestamp = models.DateTimeField(default=timezone.now()) 48 | 49 | objects = PageViewManager() 50 | 51 | def __unicode__(self): 52 | return self.path 53 | 54 | class Meta: 55 | ordering = ['-timestamp'] 56 | 57 | 58 | def page_view_received(sender, **kwargs): 59 | kwargs.pop('signal', None) 60 | page_path = kwargs.pop('page_path') 61 | primary_obj = kwargs.pop('primary_obj', None) 62 | secondary_obj = kwargs.pop('secondary_obj', None) 63 | print secondary_obj 64 | user = sender 65 | if not user.is_authenticated(): 66 | new_page_view = PageView.objects.create(path=page_path, timestamp=timezone.now()) 67 | else: 68 | new_page_view = PageView.objects.create(path=page_path, user=user, timestamp=timezone.now()) 69 | if primary_obj: 70 | new_page_view.primary_object_id = primary_obj.id 71 | new_page_view.primary_content_type = ContentType.objects.get_for_model(primary_obj) 72 | new_page_view.save() 73 | if secondary_obj: 74 | new_page_view.secondary_object_id = secondary_obj.id 75 | new_page_view.secondary_content_type = ContentType.objects.get_for_model(secondary_obj) 76 | new_page_view.save() 77 | 78 | 79 | 80 | page_view.connect(page_view_received) -------------------------------------------------------------------------------- /src/analytics/signals.py: -------------------------------------------------------------------------------- 1 | from django.dispatch import Signal 2 | 3 | 4 | page_view = Signal(providing_args=['page_path', 'primary_obj', 'secondary_obj']) -------------------------------------------------------------------------------- /src/analytics/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /src/analytics/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /src/billing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/src/billing/__init__.py -------------------------------------------------------------------------------- /src/billing/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | from .models import Membership, Transaction, UserMerchantId 5 | 6 | admin.site.register(Membership) 7 | admin.site.register(Transaction) 8 | admin.site.register(UserMerchantId) -------------------------------------------------------------------------------- /src/billing/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import datetime 6 | from django.conf import settings 7 | from django.utils.timezone import utc 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Membership', 19 | fields=[ 20 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 21 | ('date_end', models.DateTimeField(default=datetime.datetime(2015, 2, 3, 4, 34, 30, 742735, tzinfo=utc), verbose_name=b'End Date')), 22 | ('date_start', models.DateTimeField(default=datetime.datetime(2015, 2, 3, 4, 34, 30, 742756, tzinfo=utc), verbose_name=b'Start Date')), 23 | ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL)), 24 | ], 25 | options={ 26 | }, 27 | bases=(models.Model,), 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /src/billing/migrations/0002_auto_20150203_1909.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | from django.utils.timezone import utc 6 | import datetime 7 | from django.conf import settings 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ('billing', '0001_initial'), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='Transaction', 20 | fields=[ 21 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 22 | ('transaction_id', models.CharField(max_length=120)), 23 | ('order_id', models.CharField(max_length=120)), 24 | ('amount', models.DecimalField(max_digits=100, decimal_places=2)), 25 | ('success', models.BooleanField(default=True)), 26 | ('transaction_status', models.CharField(max_length=220, null=True, blank=True)), 27 | ('card_type', models.CharField(max_length=120)), 28 | ('last_four', models.PositiveIntegerField(null=True, blank=True)), 29 | ('timestamp', models.DateTimeField(auto_now_add=True)), 30 | ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)), 31 | ], 32 | options={ 33 | }, 34 | bases=(models.Model,), 35 | ), 36 | migrations.AlterField( 37 | model_name='membership', 38 | name='date_end', 39 | field=models.DateTimeField(default=datetime.datetime(2015, 2, 3, 19, 9, 28, 613098, tzinfo=utc), verbose_name=b'End Date'), 40 | preserve_default=True, 41 | ), 42 | migrations.AlterField( 43 | model_name='membership', 44 | name='date_start', 45 | field=models.DateTimeField(default=datetime.datetime(2015, 2, 3, 19, 9, 28, 613297, tzinfo=utc), verbose_name=b'Start Date'), 46 | preserve_default=True, 47 | ), 48 | ] 49 | -------------------------------------------------------------------------------- /src/billing/migrations/0003_auto_20150203_1922.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import datetime 6 | from django.utils.timezone import utc 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('billing', '0002_auto_20150203_1909'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterModelOptions( 17 | name='transaction', 18 | options={'ordering': ['-timestamp']}, 19 | ), 20 | migrations.AlterField( 21 | model_name='membership', 22 | name='date_end', 23 | field=models.DateTimeField(default=datetime.datetime(2015, 2, 3, 19, 22, 21, 455480, tzinfo=utc), verbose_name=b'End Date'), 24 | preserve_default=True, 25 | ), 26 | migrations.AlterField( 27 | model_name='membership', 28 | name='date_start', 29 | field=models.DateTimeField(default=datetime.datetime(2015, 2, 3, 19, 22, 21, 455739, tzinfo=utc), verbose_name=b'Start Date'), 30 | preserve_default=True, 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /src/billing/migrations/0004_auto_20150204_2042.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | from django.utils.timezone import utc 6 | import datetime 7 | from django.conf import settings 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ('billing', '0003_auto_20150203_1922'), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='UserMerchantId', 20 | fields=[ 21 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 22 | ('customer_id', models.CharField(max_length=120)), 23 | ('merchant_name', models.CharField(default=b'Braintree', max_length=120)), 24 | ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL)), 25 | ], 26 | options={ 27 | }, 28 | bases=(models.Model,), 29 | ), 30 | migrations.AlterField( 31 | model_name='membership', 32 | name='date_end', 33 | field=models.DateTimeField(default=datetime.datetime(2015, 2, 4, 20, 42, 27, 839118, tzinfo=utc), verbose_name=b'End Date'), 34 | preserve_default=True, 35 | ), 36 | migrations.AlterField( 37 | model_name='membership', 38 | name='date_start', 39 | field=models.DateTimeField(default=datetime.datetime(2015, 2, 4, 20, 42, 27, 839301, tzinfo=utc), verbose_name=b'Start Date'), 40 | preserve_default=True, 41 | ), 42 | ] 43 | -------------------------------------------------------------------------------- /src/billing/migrations/0005_auto_20150206_2201.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import datetime 6 | from django.utils.timezone import utc 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('billing', '0004_auto_20150204_2042'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='usermerchantid', 18 | name='plan_id', 19 | field=models.CharField(max_length=220, null=True, blank=True), 20 | preserve_default=True, 21 | ), 22 | migrations.AddField( 23 | model_name='usermerchantid', 24 | name='subscription_id', 25 | field=models.CharField(max_length=400, null=True, blank=True), 26 | preserve_default=True, 27 | ), 28 | migrations.AlterField( 29 | model_name='membership', 30 | name='date_end', 31 | field=models.DateTimeField(default=datetime.datetime(2015, 2, 6, 22, 1, 49, 533942, tzinfo=utc), verbose_name=b'End Date'), 32 | preserve_default=True, 33 | ), 34 | migrations.AlterField( 35 | model_name='membership', 36 | name='date_start', 37 | field=models.DateTimeField(default=datetime.datetime(2015, 2, 6, 22, 1, 49, 534124, tzinfo=utc), verbose_name=b'Start Date'), 38 | preserve_default=True, 39 | ), 40 | ] 41 | -------------------------------------------------------------------------------- /src/billing/migrations/0006_auto_20150206_2230.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import datetime 6 | from django.utils.timezone import utc 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('billing', '0005_auto_20150206_2201'), 13 | ] 14 | 15 | operations = [ 16 | migrations.RemoveField( 17 | model_name='usermerchantid', 18 | name='plan_id', 19 | ), 20 | migrations.RemoveField( 21 | model_name='usermerchantid', 22 | name='subscription_id', 23 | ), 24 | migrations.AlterField( 25 | model_name='membership', 26 | name='date_end', 27 | field=models.DateTimeField(default=datetime.datetime(2015, 2, 6, 22, 30, 26, 408988, tzinfo=utc), verbose_name=b'End Date'), 28 | preserve_default=True, 29 | ), 30 | migrations.AlterField( 31 | model_name='membership', 32 | name='date_start', 33 | field=models.DateTimeField(default=datetime.datetime(2015, 2, 6, 22, 30, 26, 409220, tzinfo=utc), verbose_name=b'Start Date'), 34 | preserve_default=True, 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /src/billing/migrations/0007_auto_20150206_2232.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import datetime 6 | from django.utils.timezone import utc 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('billing', '0006_auto_20150206_2230'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='usermerchantid', 18 | name='plan_id', 19 | field=models.CharField(max_length=120, null=True, blank=True), 20 | preserve_default=True, 21 | ), 22 | migrations.AddField( 23 | model_name='usermerchantid', 24 | name='subscription_id', 25 | field=models.CharField(max_length=120, null=True, blank=True), 26 | preserve_default=True, 27 | ), 28 | migrations.AlterField( 29 | model_name='membership', 30 | name='date_end', 31 | field=models.DateTimeField(default=datetime.datetime(2015, 2, 6, 22, 32, 49, 703397, tzinfo=utc), verbose_name=b'End Date'), 32 | preserve_default=True, 33 | ), 34 | migrations.AlterField( 35 | model_name='membership', 36 | name='date_start', 37 | field=models.DateTimeField(default=datetime.datetime(2015, 2, 6, 22, 32, 49, 703595, tzinfo=utc), verbose_name=b'Start Date'), 38 | preserve_default=True, 39 | ), 40 | ] 41 | -------------------------------------------------------------------------------- /src/billing/migrations/0008_auto_20150207_0140.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import datetime 6 | from django.utils.timezone import utc 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('billing', '0007_auto_20150206_2232'), 13 | ] 14 | 15 | operations = [ 16 | migrations.RemoveField( 17 | model_name='usermerchantid', 18 | name='plan_id', 19 | ), 20 | migrations.RemoveField( 21 | model_name='usermerchantid', 22 | name='subscription_id', 23 | ), 24 | migrations.AlterField( 25 | model_name='membership', 26 | name='date_end', 27 | field=models.DateTimeField(default=datetime.datetime(2015, 2, 7, 1, 40, 27, 325799, tzinfo=utc), verbose_name=b'End Date'), 28 | preserve_default=True, 29 | ), 30 | migrations.AlterField( 31 | model_name='membership', 32 | name='date_start', 33 | field=models.DateTimeField(default=datetime.datetime(2015, 2, 7, 1, 40, 27, 326015, tzinfo=utc), verbose_name=b'Start Date'), 34 | preserve_default=True, 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /src/billing/migrations/0009_auto_20150207_0206.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import datetime 6 | from django.utils.timezone import utc 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('billing', '0008_auto_20150207_0140'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='usermerchantid', 18 | name='plan_id', 19 | field=models.CharField(max_length=120, null=True, blank=True), 20 | preserve_default=True, 21 | ), 22 | migrations.AddField( 23 | model_name='usermerchantid', 24 | name='subscription_id', 25 | field=models.CharField(max_length=120, null=True, blank=True), 26 | preserve_default=True, 27 | ), 28 | migrations.AlterField( 29 | model_name='membership', 30 | name='date_end', 31 | field=models.DateTimeField(default=datetime.datetime(2015, 2, 7, 2, 6, 28, 701640, tzinfo=utc), verbose_name=b'End Date'), 32 | preserve_default=True, 33 | ), 34 | migrations.AlterField( 35 | model_name='membership', 36 | name='date_start', 37 | field=models.DateTimeField(default=datetime.datetime(2015, 2, 7, 2, 6, 28, 701842, tzinfo=utc), verbose_name=b'Start Date'), 38 | preserve_default=True, 39 | ), 40 | ] 41 | -------------------------------------------------------------------------------- /src/billing/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/src/billing/migrations/__init__.py -------------------------------------------------------------------------------- /src/billing/models.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import random 3 | 4 | 5 | from django.conf import settings 6 | from django.contrib.auth.signals import user_logged_in 7 | from django.db import models 8 | from django.db.models.signals import post_save 9 | 10 | from django.utils import timezone 11 | # Create your models here. 12 | 13 | from .signals import membership_dates_update 14 | from .utils import check_membership_status, update_braintree_membership 15 | 16 | 17 | def user_logged_in_receiver(sender, user, **kwargs): 18 | try: 19 | update_braintree_membership(user) 20 | except: 21 | pass 22 | 23 | user_logged_in.connect(user_logged_in_receiver) 24 | 25 | 26 | class Membership(models.Model): 27 | user = models.OneToOneField(settings.AUTH_USER_MODEL) 28 | date_end = models.DateTimeField(default=timezone.now(), verbose_name='End Date') 29 | date_start = models.DateTimeField(default=timezone.now(), verbose_name='Start Date') 30 | 31 | def __unicode__(self): 32 | return str(self.user.username) 33 | 34 | def update_status(self): 35 | if self.date_end >= timezone.now(): 36 | self.user.is_member = True 37 | self.user.save() 38 | elif self.date_end < timezone.now(): 39 | self.user.is_member = False 40 | self.user.save() 41 | else: 42 | pass 43 | 44 | def update_membership_status(sender, instance, created, **kwargs): 45 | if not created: 46 | instance.update_status() 47 | 48 | post_save.connect(update_membership_status, sender=Membership) 49 | 50 | 51 | 52 | def update_membership_dates(sender, new_date_start, **kwargs): 53 | membership = sender 54 | current_date_end = membership.date_end 55 | if current_date_end >= new_date_start: 56 | #append new_start date plus offset to date end of the instance 57 | membership.date_end = current_date_end + datetime.timedelta(days=30, hours=10) 58 | membership.save() 59 | 60 | else: 61 | #set a new start date and new end date with the same offset. 62 | membership.date_start = new_date_start 63 | membership.date_end = new_date_start + datetime.timedelta(days=30, hours=10) 64 | membership.save() 65 | # membership.update_status() 66 | 67 | 68 | 69 | membership_dates_update.connect(update_membership_dates) 70 | 71 | 72 | 73 | 74 | 75 | 76 | class TransactionManager(models.Manager): 77 | def create_new(self, user, transaction_id, amount, card_type,\ 78 | success=None, transaction_status=None, last_four=None): 79 | if not user: 80 | raise ValueError("Must be a user") 81 | if not transaction_id: 82 | raise ValueError("Must complete a transaction to add new.") 83 | new_order_id = "%s%s%s" %(transaction_id[:2],random.randint(1, 23423),transaction_id[2:]) 84 | new_trans = self.model( 85 | user = user, 86 | transaction_id = transaction_id, 87 | order_id= new_order_id, 88 | amount = amount, 89 | card_type=card_type 90 | ) 91 | if success is not None: 92 | new_trans.success = success 93 | new_trans.transaction_status = transaction_status 94 | if last_four is not None: 95 | new_trans.last_four = last_four 96 | new_trans.save(using=self._db) 97 | return new_trans 98 | 99 | def all_for_user(self, user): 100 | return super(TransactionManager, self).filter(user=user) 101 | 102 | def get_recent_for_user(self, user, num): 103 | return super(TransactionManager, self).filter(user=user)[:num] 104 | 105 | 106 | 107 | class Transaction(models.Model): 108 | user = models.ForeignKey(settings.AUTH_USER_MODEL) 109 | transaction_id = models.CharField(max_length=120) #braintree or stripe 110 | order_id = models.CharField(max_length=120) 111 | amount = models.DecimalField(max_digits=100, decimal_places=2) 112 | success = models.BooleanField(default=True) 113 | transaction_status = models.CharField(max_length=220, null=True, blank=True) #if fails 114 | card_type = models.CharField(max_length=120) #paypal 115 | last_four = models.PositiveIntegerField(null=True, blank=True) 116 | timestamp = models.DateTimeField(auto_now_add=True, auto_now=False) 117 | 118 | objects = TransactionManager() 119 | 120 | def __unicode__(self): 121 | return self.order_id 122 | 123 | class Meta: 124 | ordering = ['-timestamp'] 125 | 126 | 127 | 128 | 129 | class UserMerchantId(models.Model): 130 | user = models.OneToOneField(settings.AUTH_USER_MODEL) 131 | customer_id = models.CharField(max_length=120) 132 | subscription_id = models.CharField(max_length=120, null=True, blank=True) 133 | plan_id = models.CharField(max_length=120, null=True, blank=True) 134 | merchant_name = models.CharField(max_length=120, default="Braintree") 135 | 136 | def __unicode__(self): 137 | return self.customer_id 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /src/billing/signals.py: -------------------------------------------------------------------------------- 1 | from django.dispatch import Signal 2 | 3 | 4 | 5 | membership_dates_update = Signal(providing_args=['new_date_start']) -------------------------------------------------------------------------------- /src/billing/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /src/billing/utils.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from django.conf import settings 4 | from django.utils import timezone 5 | 6 | import braintree 7 | 8 | braintree.Configuration.configure(braintree.Environment.Sandbox, 9 | merchant_id=settings.BRAINTREE_MERCHANT_ID, 10 | public_key=settings.BRAINTREE_PUBLIC_KEY, 11 | private_key=settings.BRAINTREE_PRIVATE_KEY) 12 | 13 | 14 | 15 | from .signals import membership_dates_update 16 | 17 | 18 | def check_membership_status(subscription_id): 19 | sub = braintree.Subscription.find(subscription_id) 20 | if sub.status == "Active": 21 | status = True 22 | next_billing_date = sub.next_billing_date 23 | else: 24 | status = False 25 | next_billing_date = None 26 | # checking in braintree 27 | return status, next_billing_date 28 | 29 | 30 | def update_braintree_membership(user): 31 | user = user 32 | membership = user.membership 33 | now = timezone.now() 34 | subscription_id = user.usermerchantid.subscription_id 35 | if membership.date_end <= timezone.now() and subscription_id is not None: 36 | status, next_billing_date = check_membership_status(subscription_id) 37 | if status: 38 | small_time = datetime.time(0,0,0,1) 39 | datetime_obj = datetime.datetime.combine(next_billing_date, small_time) 40 | datetime_aware = timezone.make_aware(datetime_obj, timezone.get_current_timezone()) 41 | membership_dates_update.send(membership, new_date_start=datetime_aware) 42 | else: 43 | membership.update_status() 44 | elif subscription_id is None: 45 | membership.update_status() 46 | else: 47 | pass 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/comments/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/src/comments/__init__.py -------------------------------------------------------------------------------- /src/comments/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | from .models import Comment 5 | 6 | 7 | class CommentAdmin(admin.ModelAdmin): 8 | list_display = ['__unicode__', 'text'] 9 | class Meta: 10 | model = Comment 11 | 12 | admin.site.register(Comment, CommentAdmin) -------------------------------------------------------------------------------- /src/comments/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | 4 | from crispy_forms.helper import FormHelper 5 | from crispy_forms.layout import Submit 6 | 7 | from .models import Comment 8 | 9 | 10 | # class CommentForm(forms.ModelForm): 11 | # class Meta: 12 | # model = Comment 13 | # fields = ('user', 'path', 'text') 14 | 15 | class CommentForm(forms.Form): 16 | comment = forms.CharField( 17 | widget=forms.Textarea(attrs={"placeholder": "Your comment or reply."}) 18 | ) 19 | 20 | def __init__(self, data=None, files=None, **kwargs): 21 | super(CommentForm, self).__init__(data, files, kwargs) 22 | self.helper = FormHelper() 23 | self.helper.form_show_labels = False 24 | self.helper.add_input(Submit('submit', 'Add Comment', css_class='btn btn-primary',)) -------------------------------------------------------------------------------- /src/comments/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | from django.conf import settings 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ('videos', '0010_auto_20150120_2335'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='Comment', 18 | fields=[ 19 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 20 | ('path', models.CharField(max_length=350)), 21 | ('text', models.TextField()), 22 | ('updated', models.DateTimeField(auto_now=True)), 23 | ('timestamp', models.DateTimeField(auto_now_add=True)), 24 | ('active', models.BooleanField(default=True)), 25 | ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)), 26 | ('video', models.ForeignKey(blank=True, to='videos.Video', null=True)), 27 | ], 28 | options={ 29 | }, 30 | bases=(models.Model,), 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /src/comments/migrations/0002_comment_parent.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('comments', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='comment', 16 | name='parent', 17 | field=models.ForeignKey(blank=True, to='comments.Comment', null=True), 18 | preserve_default=True, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/comments/migrations/0003_auto_20150125_0038.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('comments', '0002_comment_parent'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterModelOptions( 15 | name='comment', 16 | options={'ordering': ['-timestamp']}, 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /src/comments/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/src/comments/migrations/__init__.py -------------------------------------------------------------------------------- /src/comments/models.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.core.urlresolvers import reverse 3 | from django.db import models 4 | from django.template.defaultfilters import truncatechars 5 | from django.utils.text import Truncator 6 | 7 | # Create your models here. 8 | from accounts.models import MyUser 9 | from videos.models import Video 10 | 11 | 12 | class CommentManager(models.Manager): 13 | def all(self): 14 | return super(CommentManager, self).filter(active=True).filter(parent=None) 15 | 16 | def recent(self): 17 | try: 18 | limit_to = settings.RECENT_COMMENT_NUMBER 19 | except: 20 | limit_to = 6 21 | return self.get_queryset().filter(active=True).filter(parent=None)[:limit_to] 22 | 23 | def create_comment(self, user=None, text=None, path=None, video=None, parent=None): 24 | if not path: 25 | raise ValueError("Must include a path when adding a Comment") 26 | if not user: 27 | raise ValueError("Must include a user when adding a Comment") 28 | 29 | comment = self.model( 30 | user = user, 31 | path = path, 32 | text = text 33 | ) 34 | if video is not None: 35 | comment.video = video 36 | if parent is not None: 37 | comment.parent = parent 38 | comment.save(using=self._db) 39 | return comment 40 | 41 | 42 | class Comment(models.Model): 43 | user = models.ForeignKey(MyUser) 44 | parent = models.ForeignKey("self", null=True, blank=True) 45 | path = models.CharField(max_length=350) 46 | video = models.ForeignKey(Video, null=True, blank=True) 47 | text = models.TextField() 48 | updated = models.DateTimeField(auto_now=True, auto_now_add=False) 49 | timestamp = models.DateTimeField(auto_now=False, auto_now_add=True) 50 | active = models.BooleanField(default=True) 51 | 52 | objects = CommentManager() 53 | 54 | class Meta: 55 | ordering = ['-timestamp'] 56 | 57 | def __unicode__(self): 58 | return self.text 59 | 60 | 61 | def get_absolute_url(self): 62 | return reverse('comment_thread', kwargs={"id": self.id}) 63 | 64 | @property 65 | def get_origin(self): 66 | return self.path 67 | 68 | @property 69 | def get_preview(self): 70 | #return truncatechars(self.text, 120) 71 | return Truncator(self.text).chars(120) 72 | 73 | @property 74 | def get_comment(self): 75 | return self.text 76 | 77 | @property 78 | def is_child(self): 79 | if self.parent is not None: 80 | return True 81 | else: 82 | return False 83 | 84 | def get_children(self): 85 | if self.is_child: 86 | return None 87 | else: 88 | return Comment.objects.filter(parent=self) 89 | 90 | def get_affected_users(self): 91 | """ 92 | it needs to be a parent and have children, 93 | the children, in effect, are the affected users. 94 | """ 95 | comment_children = self.get_children() 96 | if comment_children is not None: 97 | users = [] 98 | for comment in comment_children: 99 | if comment.user in users: 100 | pass 101 | else: 102 | users.append(comment.user) 103 | return users 104 | return None 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /src/comments/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /src/comments/views.py: -------------------------------------------------------------------------------- 1 | 2 | from django.contrib import messages 3 | from django.contrib.auth.decorators import login_required 4 | from django.shortcuts import render, Http404, HttpResponseRedirect, get_object_or_404 5 | 6 | # Create your views here. 7 | 8 | from notifications.signals import notify 9 | 10 | from videos.models import Video 11 | 12 | from .models import Comment 13 | from .forms import CommentForm 14 | 15 | 16 | @login_required 17 | def comment_thread(request, id): 18 | comment = get_object_or_404(Comment, id=id) 19 | form = CommentForm() 20 | context = { 21 | "form": form, 22 | "comment": comment, 23 | } 24 | return render(request, "comments/comment_thread.html", context) 25 | 26 | 27 | 28 | def comment_create_view(request): 29 | if request.method == "POST" and request.user.is_authenticated(): 30 | parent_id = request.POST.get('parent_id') 31 | video_id = request.POST.get("video_id") 32 | origin_path = request.POST.get("origin_path") 33 | try: 34 | video = Video.objects.get(id=video_id) 35 | except: 36 | video = None 37 | 38 | print video 39 | parent_comment = None 40 | if parent_id is not None: 41 | try: 42 | parent_comment = Comment.objects.get(id=parent_id) 43 | except: 44 | parent_comment = None 45 | 46 | if parent_comment is not None and parent_comment.video is not None: 47 | video = parent_comment.video 48 | 49 | form = CommentForm(request.POST) 50 | if form.is_valid(): 51 | comment_text = form.cleaned_data['comment'] 52 | if parent_comment is not None: 53 | # parent comments exists 54 | new_comment = Comment.objects.create_comment( 55 | user=request.user, 56 | path=parent_comment.get_origin, 57 | text=comment_text, 58 | video = video, 59 | parent=parent_comment 60 | ) 61 | affected_users = parent_comment.get_affected_users() 62 | notify.send( 63 | request.user, 64 | action=new_comment, 65 | target=parent_comment, 66 | recipient=parent_comment.user, 67 | affected_users = affected_users, 68 | verb='replied to') 69 | messages.success(request, "Thank you for your response.", extra_tags='safe') 70 | return HttpResponseRedirect(parent_comment.get_absolute_url()) 71 | else: 72 | new_comment = Comment.objects.create_comment( 73 | user=request.user, 74 | path=origin_path, 75 | text=comment_text, 76 | video = video 77 | ) 78 | # option to send to super user or staff users 79 | # notify.send( 80 | # request.user, 81 | # recipient = request.user, 82 | # action=new_comment, 83 | # target = new_comment.video, 84 | # verb='commented on') 85 | #notify.send(request.user, recipient=request.user, action='New comment added') 86 | messages.success(request, "Thank you for the comment.") 87 | return HttpResponseRedirect(new_comment.get_absolute_url()) 88 | else: 89 | print origin_path 90 | messages.error(request, "There was an error with your comment.") 91 | return HttpResponseRedirect(origin_path) 92 | 93 | else: 94 | raise Http404 95 | -------------------------------------------------------------------------------- /src/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/src/db.sqlite3 -------------------------------------------------------------------------------- /src/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "srvup.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /src/notifications/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/src/notifications/__init__.py -------------------------------------------------------------------------------- /src/notifications/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | from .models import Notification 5 | 6 | 7 | admin.site.register(Notification) -------------------------------------------------------------------------------- /src/notifications/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | from django.conf import settings 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Notification', 17 | fields=[ 18 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 19 | ('action', models.CharField(max_length=255)), 20 | ('timestamp', models.DateTimeField(auto_now_add=True)), 21 | ('recipient', models.ForeignKey(related_name='notifications', to=settings.AUTH_USER_MODEL)), 22 | ], 23 | options={ 24 | }, 25 | bases=(models.Model,), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /src/notifications/migrations/0002_auto_20150126_1924.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('contenttypes', '0001_initial'), 11 | ('notifications', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameField( 16 | model_name='notification', 17 | old_name='action', 18 | new_name='verb', 19 | ), 20 | migrations.AddField( 21 | model_name='notification', 22 | name='action_content_type', 23 | field=models.ForeignKey(related_name='notify_action', blank=True, to='contenttypes.ContentType', null=True), 24 | preserve_default=True, 25 | ), 26 | migrations.AddField( 27 | model_name='notification', 28 | name='action_object_id', 29 | field=models.PositiveIntegerField(default=0), 30 | preserve_default=False, 31 | ), 32 | migrations.AddField( 33 | model_name='notification', 34 | name='sender_content_type', 35 | field=models.ForeignKey(related_name='nofity_sender', default=1, to='contenttypes.ContentType'), 36 | preserve_default=False, 37 | ), 38 | migrations.AddField( 39 | model_name='notification', 40 | name='sender_object_id', 41 | field=models.PositiveIntegerField(default=0), 42 | preserve_default=False, 43 | ), 44 | migrations.AddField( 45 | model_name='notification', 46 | name='target_content_type', 47 | field=models.ForeignKey(related_name='notify_target', blank=True, to='contenttypes.ContentType', null=True), 48 | preserve_default=True, 49 | ), 50 | migrations.AddField( 51 | model_name='notification', 52 | name='target_object_id', 53 | field=models.PositiveIntegerField(default=0), 54 | preserve_default=False, 55 | ), 56 | ] 57 | -------------------------------------------------------------------------------- /src/notifications/migrations/0003_auto_20150126_2134.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('notifications', '0002_auto_20150126_1924'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='notification', 16 | name='action_object_id', 17 | field=models.PositiveIntegerField(null=True, blank=True), 18 | preserve_default=True, 19 | ), 20 | migrations.AlterField( 21 | model_name='notification', 22 | name='target_object_id', 23 | field=models.PositiveIntegerField(null=True, blank=True), 24 | preserve_default=True, 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /src/notifications/migrations/0004_auto_20150126_2212.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('notifications', '0003_auto_20150126_2134'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='notification', 16 | name='read', 17 | field=models.BooleanField(default=False), 18 | preserve_default=True, 19 | ), 20 | migrations.AddField( 21 | model_name='notification', 22 | name='unread', 23 | field=models.BooleanField(default=True), 24 | preserve_default=True, 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /src/notifications/migrations/0005_remove_notification_unread.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('notifications', '0004_auto_20150126_2212'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='notification', 16 | name='unread', 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /src/notifications/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/src/notifications/migrations/__init__.py -------------------------------------------------------------------------------- /src/notifications/models.py: -------------------------------------------------------------------------------- 1 | 2 | from django.conf import settings 3 | from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation 4 | from django.contrib.contenttypes.models import ContentType 5 | from django.core.urlresolvers import reverse 6 | from django.db import models 7 | 8 | from .signals import notify 9 | # Create your models here. 10 | 11 | class NotificationQuerySet(models.query.QuerySet): 12 | def get_user(self, recipient): 13 | return self.filter(recipient=recipient) 14 | 15 | def mark_targetless(self, recipient): 16 | qs = self.unread().get_user(recipient) 17 | qs_no_target = qs.filter(target_object_id=None) 18 | if qs_no_target: 19 | qs_no_target.update(read=True) 20 | 21 | def mark_all_read(self, recipient): 22 | qs = self.unread().get_user(recipient) 23 | qs.update(read=True) 24 | 25 | def mark_all_unread(self, recipient): 26 | qs = self.read().get_user(recipient) 27 | qs.update(read=False) 28 | 29 | def unread(self): 30 | return self.filter(read=False) 31 | 32 | def read(self): 33 | return self.filter(read=True) 34 | 35 | def recent(self): 36 | return self.unread()[:5] 37 | 38 | 39 | class NotificationManager(models.Manager): 40 | def get_queryset(self): 41 | return NotificationQuerySet(self.model, using=self._db) 42 | 43 | def all_unread(self, user): 44 | return self.get_queryset().get_user(user).unread() 45 | 46 | def all_read(self, user): 47 | return self.get_queryset().get_user(user).read() 48 | 49 | def all_for_user(self, user): 50 | self.get_queryset().mark_targetless(user) 51 | return self.get_queryset().get_user(user) 52 | 53 | def get_recent_for_user(self, user, num): 54 | return self.get_queryset().get_user(user)[:num] 55 | 56 | 57 | class Notification(models.Model): 58 | sender_content_type = models.ForeignKey(ContentType, related_name='nofity_sender') 59 | sender_object_id = models.PositiveIntegerField() 60 | sender_object = GenericForeignKey("sender_content_type", "sender_object_id") 61 | 62 | verb = models.CharField(max_length=255) 63 | 64 | action_content_type = models.ForeignKey(ContentType, related_name='notify_action', 65 | null=True, blank=True) 66 | action_object_id = models.PositiveIntegerField(null=True, blank=True) 67 | action_object = GenericForeignKey("action_content_type", "action_object_id") 68 | 69 | target_content_type = models.ForeignKey(ContentType, related_name='notify_target', 70 | null=True, blank=True) 71 | target_object_id = models.PositiveIntegerField(null=True, blank=True) 72 | target_object = GenericForeignKey("target_content_type", "target_object_id") 73 | 74 | recipient = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='notifications') 75 | read = models.BooleanField(default=False) 76 | timestamp = models.DateTimeField(auto_now_add=True, auto_now=False) 77 | 78 | objects = NotificationManager() 79 | 80 | 81 | def __unicode__(self): 82 | try: 83 | target_url = self.target_object.get_absolute_url() 84 | except: 85 | target_url = None 86 | context = { 87 | "sender": self.sender_object, 88 | "verb": self.verb, 89 | "action": self.action_object, 90 | "target": self.target_object, 91 | "verify_read": reverse("notifications_read", kwargs={"id": self.id}), 92 | "target_url": target_url, 93 | } 94 | if self.target_object: 95 | if self.action_object and target_url: 96 | return "%(sender)s %(verb)s %(target)s with %(action)s" %context 97 | if self.action_object and not target_url: 98 | return "%(sender)s %(verb)s %(target)s with %(action)s" %context 99 | return "%(sender)s %(verb)s %(target)s" %context 100 | return "%(sender)s %(verb)s" %context 101 | 102 | @property 103 | def get_link(self): 104 | try: 105 | target_url = self.target_object.get_absolute_url() 106 | except: 107 | target_url = reverse("notifications_all") 108 | 109 | context = { 110 | "sender": self.sender_object, 111 | "verb": self.verb, 112 | "action": self.action_object, 113 | "target": self.target_object, 114 | "verify_read": reverse("notifications_read", kwargs={"id": self.id}), 115 | "target_url": target_url, 116 | } 117 | if self.target_object: 118 | return "%(sender)s %(verb)s %(target)s with %(action)s" %context 119 | else: 120 | return "%(sender)s %(verb)s" %context 121 | 122 | 123 | 124 | def new_notification(sender, **kwargs): 125 | kwargs.pop('signal', None) 126 | recipient = kwargs.pop("recipient") 127 | verb = kwargs.pop("verb") 128 | #verb = kwargs["verb"] 129 | affected_users = kwargs.pop('affected_users', None) 130 | #print "affected users are" 131 | #print affected_users 132 | #print sender 133 | 134 | #target = kwargs.pop("target", None) 135 | #action = kwargs.pop("action", None) 136 | if affected_users is not None: 137 | for u in affected_users: 138 | if u == sender: 139 | pass 140 | else: 141 | print u 142 | new_note = Notification( 143 | recipient=u, 144 | verb = verb, # smart_text 145 | sender_content_type = ContentType.objects.get_for_model(sender), 146 | sender_object_id = sender.id, 147 | ) 148 | for option in ("target", "action"): 149 | #obj = kwargs.pop(option, None) 150 | try: 151 | obj = kwargs[option] 152 | if obj is not None: 153 | setattr(new_note, "%s_content_type" %option, ContentType.objects.get_for_model(obj)) 154 | setattr(new_note, "%s_object_id" %option, obj.id) 155 | except: 156 | pass 157 | new_note.save() 158 | print new_note 159 | else: 160 | new_note = Notification( 161 | recipient=recipient, 162 | verb = verb, # smart_text 163 | sender_content_type = ContentType.objects.get_for_model(sender), 164 | sender_object_id = sender.id, 165 | ) 166 | for option in ("target", "action"): 167 | obj = kwargs.pop(option, None) 168 | if obj is not None: 169 | setattr(new_note, "%s_content_type" %option, ContentType.objects.get_for_model(obj)) 170 | setattr(new_note, "%s_object_id" %option, obj.id) 171 | new_note.save() 172 | #print new_note 173 | 174 | 175 | notify.connect(new_notification) 176 | 177 | # justin (AUTH_USER_MODEL) 178 | # has commented ("verb") 179 | # with a Comment (id=32) (instance action_object) 180 | # on your Comment (id=12) (targeted instance) 181 | # so now you should know about it (AUTH_USER_MODEL) 182 | 183 | # 184 | # #verb to 185 | # #to 186 | # #tell 187 | # 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | -------------------------------------------------------------------------------- /src/notifications/signals.py: -------------------------------------------------------------------------------- 1 | from django.dispatch import Signal 2 | 3 | 4 | notify = Signal(providing_args=['recipient', 'verb', 'action', 'target', 'affected_users']) 5 | 6 | 7 | """ 8 | 9 | sender_content_type = models.ForeignKey(ContentType, related_name='nofity_sender') 10 | sender_object_id = models.PositiveIntegerField() 11 | sender_object = GenericForeignKey("sender_content_type", "sender_object_id") 12 | 13 | verb = models.CharField(max_length=255) 14 | 15 | action_content_type = models.ForeignKey(ContentType, related_name='notify_action', 16 | null=True, blank=True) 17 | action_object_id = models.PositiveIntegerField() 18 | action_object = GenericForeignKey("action_content_type", "action_object_id") 19 | 20 | target_content_type = models.ForeignKey(ContentType, related_name='notify_target', 21 | null=True, blank=True) 22 | target_object_id = models.PositiveIntegerField() 23 | target_object = GenericForeignKey("target_content_type", "target_object_id") 24 | 25 | recipient = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='notifications') 26 | 27 | #read 28 | #unread 29 | timestamp = models.DateTimeField(auto_now_add=True, auto_now=False) 30 | """ -------------------------------------------------------------------------------- /src/notifications/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /src/notifications/views.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from django.contrib.auth.decorators import login_required 4 | from django.core.urlresolvers import reverse 5 | from django.http import HttpResponse 6 | from django.shortcuts import render, Http404, HttpResponseRedirect, redirect, get_object_or_404 7 | 8 | from .models import Notification 9 | # Create your views here. 10 | 11 | @login_required 12 | def all(request): 13 | notifications = Notification.objects.all_for_user(request.user) 14 | context = { 15 | "notifications":notifications, 16 | } 17 | return render(request, "notifications/all.html", context) 18 | 19 | @login_required 20 | def read(request, id): 21 | notification = get_object_or_404(Notification, id=id) 22 | try: 23 | next = request.GET.get('next', None) 24 | if notification.recipient == request.user: 25 | notification.read = True 26 | notification.save() 27 | if next is not None: 28 | return HttpResponseRedirect(next) 29 | else: 30 | return redirect("notifications_all") 31 | else: 32 | raise Http404 33 | except: 34 | raise redirect("notifications_all") 35 | 36 | 37 | @login_required 38 | def get_notifications_ajax(request): 39 | if request.is_ajax() and request.method == "POST": 40 | notifications = Notification.objects.all_for_user(request.user).recent() 41 | count = notifications.count() 42 | notes = [] 43 | for note in notifications: 44 | notes.append(str(note.get_link)) 45 | data = { 46 | "notifications": notes, 47 | "count": count, 48 | } 49 | print data 50 | json_data = json.dumps(data) 51 | print json_data 52 | return HttpResponse(json_data, content_type='application/json') 53 | else: 54 | raise Http404 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/srvup/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/src/srvup/__init__.py -------------------------------------------------------------------------------- /src/srvup/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/src/srvup/__init__.pyc -------------------------------------------------------------------------------- /src/srvup/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/srvup/forms.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/src/srvup/forms.pyc -------------------------------------------------------------------------------- /src/srvup/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for srvup project. 3 | 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/1.7/topics/settings/ 6 | 7 | For the full list of settings and their values, see 8 | https://docs.djangoproject.com/en/1.7/ref/settings/ 9 | """ 10 | 11 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 12 | import os 13 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 14 | 15 | 16 | # Quick-start development settings - unsuitable for production 17 | # See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/ 18 | 19 | # SECURITY WARNING: keep the secret key used in production secret! 20 | SECRET_KEY = '6j6^%gd-su1^j7qmp&4g-gm$_nvs2hx(og&ovlj#1)+@09@p87' 21 | 22 | # SECURITY WARNING: don't run with debug turned on in production! 23 | DEBUG = True 24 | 25 | TEMPLATE_DEBUG = True 26 | 27 | ALLOWED_HOSTS = [] 28 | FULL_DOMAIN_NAME = 'http://www.codingforentrepreneurs.com' 29 | 30 | LOGIN_URL = "/login/" 31 | 32 | AUTH_USER_MODEL = 'accounts.MyUser' 33 | RECENT_COMMENT_NUMBER = 10 34 | # Application definition 35 | 36 | INSTALLED_APPS = ( 37 | 'django.contrib.admin', 38 | 'django.contrib.auth', 39 | 'django.contrib.contenttypes', 40 | 'django.contrib.sessions', 41 | 'django.contrib.messages', 42 | 'django.contrib.staticfiles', 43 | "crispy_forms", 44 | 'accounts', 45 | "analytics", 46 | "billing", 47 | 'comments', 48 | 'notifications', 49 | 'videos', 50 | ) 51 | 52 | CRISPY_TEMPLATE_PACK = "bootstrap3" 53 | 54 | MIDDLEWARE_CLASSES = ( 55 | 'django.contrib.sessions.middleware.SessionMiddleware', 56 | 'django.middleware.common.CommonMiddleware', 57 | 'django.middleware.csrf.CsrfViewMiddleware', 58 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 59 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 60 | 'django.contrib.messages.middleware.MessageMiddleware', 61 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 62 | ) 63 | 64 | 65 | TEMPLATE_CONTEXT_PROCESSORS =( 66 | "django.contrib.auth.context_processors.auth", 67 | "django.core.context_processors.debug", 68 | "django.core.context_processors.i18n", 69 | "django.core.context_processors.media", 70 | "django.core.context_processors.request", 71 | "django.core.context_processors.static", 72 | "django.core.context_processors.tz", 73 | "django.contrib.messages.context_processors.messages" 74 | ) 75 | 76 | ROOT_URLCONF = 'srvup.urls' 77 | 78 | WSGI_APPLICATION = 'srvup.wsgi.application' 79 | 80 | 81 | # Database 82 | # https://docs.djangoproject.com/en/1.7/ref/settings/#databases 83 | 84 | DATABASES = { 85 | 'default': { 86 | 'ENGINE': 'django.db.backends.sqlite3', 87 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 88 | } 89 | } 90 | 91 | # Internationalization 92 | # https://docs.djangoproject.com/en/1.7/topics/i18n/ 93 | 94 | LANGUAGE_CODE = 'en-us' 95 | 96 | TIME_ZONE = 'UTC' 97 | 98 | USE_I18N = True 99 | 100 | USE_L10N = True 101 | 102 | USE_TZ = True 103 | 104 | 105 | # Static files (CSS, JavaScript, Images) 106 | # https://docs.djangoproject.com/en/1.7/howto/static-files/ 107 | 108 | STATIC_URL = '/static/' 109 | 110 | STATICFILES_DIRS = ( 111 | os.path.join(os.path.dirname(BASE_DIR), "static", "static_dirs"), 112 | #'/Users/jmitch/Desktop/srvup/static/static_dirs/', #on mac 113 | #'\Users\jmitch\Desktop\srvup\static\static_dirs\', somethingl ike this on windows 114 | #'/var/www/static/', 115 | ) 116 | 117 | STATIC_ROOT = os.path.join(os.path.dirname(BASE_DIR), "static", "static_root") 118 | 119 | TEMPLATE_DIRS = ( 120 | os.path.join(BASE_DIR, "templates"), 121 | ) 122 | 123 | 124 | MEDIA_URL = '/media/' 125 | MEDIA_ROOT = os.path.join(os.path.dirname(BASE_DIR), "static", "media") 126 | 127 | 128 | 129 | #braintree info 130 | BRAINTREE_MERCHANT_ID ="3j27nwdw8mbvk68y" 131 | BRAINTREE_PUBLIC_KEY = "64zrsxstnhykn4v2" 132 | BRAINTREE_PRIVATE_KEY = "5507587264ea632357cad014f69ed78f" 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /src/srvup/settings.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/src/srvup/settings.pyc -------------------------------------------------------------------------------- /src/srvup/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls import patterns, include, url 3 | from django.conf.urls.static import static 4 | 5 | from django.contrib import admin 6 | from django.views.generic import TemplateView 7 | 8 | urlpatterns = patterns('', 9 | # Examples: 10 | #url(r'^about/$', TemplateView.as_view(template_name='base.html'), name='home'), 11 | #url(r'^pricing/$', TemplateView.as_view(template_name='base.html'), name='home'), 12 | url(r'^contact/$', TemplateView.as_view(template_name='company/contact_us.html'), name='contact_us'), 13 | url(r'^$', 'srvup.views.home', name='home'), 14 | url(r'^projects/$', 'videos.views.category_list', name='projects'), 15 | url(r'^projects/(?P[\w-]+)/$', 'videos.views.category_detail', name='project_detail'), 16 | url(r'^projects/(?P[\w-]+)/(?P[\w-]+)/$', 'videos.views.video_detail', name='video_detail'), 17 | url(r'^dj/admin/', include(admin.site.urls)), 18 | ) 19 | 20 | if settings.DEBUG: 21 | urlpatterns += patterns('',) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) 22 | urlpatterns += patterns('',) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 23 | 24 | 25 | 26 | #auth login/logout 27 | urlpatterns += patterns('billing.views', 28 | url(r'^upgrade/$', 'upgrade', name='account_upgrade'), 29 | url(r'^billing/$', 'billing_history', name='billing_history'), 30 | url(r'^billing/cancel/$', 'cancel_subscription', name='cancel_subscription'), 31 | ) 32 | 33 | 34 | 35 | #auth login/logout 36 | urlpatterns += patterns('accounts.views', 37 | url(r'^account/$', 'account_home', name='account_home'), 38 | url(r'^logout/$', 'auth_logout', name='logout'), 39 | url(r'^login/$', 'auth_login', name='login'), 40 | url(r'^register/$', 'auth_register', name='register'), 41 | ) 42 | 43 | 44 | 45 | #Comment Thread 46 | urlpatterns += patterns('comments.views', 47 | url(r'^comment/(?P\d+)$', 'comment_thread', name='comment_thread'), 48 | url(r'^comment/create/$', 'comment_create_view', name='comment_create'), 49 | ) 50 | 51 | 52 | #Notifications 53 | urlpatterns += patterns('notifications.views', 54 | url(r'^notifications/$', 'all', name='notifications_all'), 55 | url(r'^notifications/ajax/$', 'get_notifications_ajax', name='get_notifications_ajax'), 56 | url(r'^notifications/(?P\d+)/$', 'read', name='notifications_read'), 57 | 58 | ) 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/srvup/urls.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/src/srvup/urls.pyc -------------------------------------------------------------------------------- /src/srvup/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import authenticate, login, logout 2 | from django.contrib.auth.decorators import login_required 3 | from django.contrib.contenttypes.models import ContentType 4 | from django.db.models import Count 5 | from django.shortcuts import render, HttpResponseRedirect, redirect 6 | from django.utils.safestring import mark_safe 7 | 8 | 9 | 10 | from accounts.forms import RegisterForm, LoginForm 11 | from accounts.models import MyUser 12 | 13 | from analytics.models import PageView 14 | from analytics.signals import page_view 15 | from comments.models import Comment 16 | from videos.models import Video, Category 17 | 18 | 19 | 20 | 21 | #@login_required(login_url='/enroll/login/') 22 | #@login_required 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | def home(request): 33 | 34 | page_view.send( 35 | request.user, 36 | page_path=request.get_full_path() 37 | ) 38 | if request.user.is_authenticated(): 39 | page_view_objs = request.user.pageview_set.get_videos()[:6] 40 | 41 | recent_videos = [] 42 | for obj in page_view_objs: 43 | if not obj.primary_object in recent_videos: 44 | #print obj.primary_object 45 | recent_videos.append(obj.primary_object) 46 | recent_comments = Comment.objects.recent() 47 | 48 | #top items 49 | video_type = ContentType.objects.get_for_model(Video) 50 | popular_videos_list = PageView.objects.filter(primary_content_type=video_type)\ 51 | .values("primary_object_id")\ 52 | .annotate(the_count=Count("primary_object_id"))\ 53 | .order_by("-the_count")[:4] 54 | popular_videos = [] 55 | for item in popular_videos_list: 56 | try: 57 | new_video = Video.objects.get(id=item['primary_object_id']) 58 | popular_videos.append(new_video) 59 | except: 60 | pass 61 | 62 | random_videos = Video.objects.all().order_by('?')[:6] 63 | #print popular_videos 64 | # one item 65 | #PageView.objects.filter(primary_content_type=video_type, primary_object_id=21).count() 66 | 67 | 68 | context = { 69 | "random_videos": random_videos, 70 | "recent_videos": recent_videos, 71 | "recent_comments": recent_comments, 72 | "popular_videos": popular_videos, 73 | } 74 | template = "accounts/home_logged_in.html" 75 | #return HttpResponseRedirect('/dashboard/') 76 | else: 77 | featured_categories = Category.objects.get_featured() 78 | featured_videos = Video.objects.get_featured() 79 | login_form = LoginForm() 80 | register_form = RegisterForm() 81 | template = "accounts/home_visitor.html" 82 | context = { 83 | "register_form": register_form, 84 | "login_form": login_form, 85 | "featured_videos":featured_videos, 86 | "featured_categories": featured_categories, 87 | } 88 | 89 | return render(request,template,context) 90 | 91 | 92 | 93 | 94 | # def home(request): 95 | # if request.user.is_authenticated(): 96 | # print 97 | # name = "Justin" 98 | # videos = Video.objects.all() 99 | # embeds = [] 100 | 101 | # for vid in videos: 102 | # code = mark_safe(vid.embed_code) 103 | # embeds.append("%s" %(code)) 104 | 105 | # context = { 106 | # "the_name": name, 107 | # "number": videos.count(), 108 | # "videos": videos, 109 | # "the_embeds": embeds, 110 | # "a_code": mark_safe(videos[0].embed_code) 111 | # } 112 | # return render(request, "home.html", context) 113 | # #redirect to login 114 | # else: 115 | # return HttpResponseRedirect('/login/') 116 | 117 | 118 | 119 | @login_required(login_url='/staff/login/') 120 | def staff_home(request): 121 | context = { 122 | 123 | } 124 | return render(request, "home.html", context) 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /src/srvup/views.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/src/srvup/views.pyc -------------------------------------------------------------------------------- /src/srvup/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for srvup project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "srvup.settings") 12 | 13 | from django.core.wsgi import get_wsgi_application 14 | application = get_wsgi_application() 15 | -------------------------------------------------------------------------------- /src/srvup/wsgi.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/src/srvup/wsgi.pyc -------------------------------------------------------------------------------- /src/templates/accounts/account_home.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | 4 | 5 | {% block content %} 6 | 7 | 8 |
9 |
10 |

Account

11 |
12 |
13 |
14 | 15 |
16 | 17 |
18 |

Notifications 19 | View All

20 | 21 | {% for note in notifications %} 22 | 23 | 24 | 25 | {% endfor %} 26 |
{{ note|safe }}{% if note.read %} Read {% else %} Unread {% endif %}
27 | 28 |
29 |
30 | 31 | 32 |
33 |
34 |
35 |

Account Options

36 |

37 | {% if request.user.is_member %} 38 | Account active until: {{ request.user.membership.date_end }} 39 | {% endif %} 40 | 41 |

42 | {% if request.user.is_member and request.user.usermerchantid.subscription_id %} 43 | 44 | 45 | Cancel Membership 46 | 47 | {% elif not request.user.is_member %} 48 | 49 | Upgrade to Membership 50 | 51 | {% else %} 52 | 53 | 54 | 55 | {% endif %} 56 |
57 | 58 | 59 |
60 | 61 |
62 | 63 |
64 |
65 |

Recent Billing History 66 | View All 67 | 68 |

69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | {% for object in transactions %} 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | {% endfor %} 88 | 89 |
Order #AmountPayment MethodDate
{{ object.order_id }}${{ object.amount }}{{ object.card_type }}{% if object.last_four %}
Last Four Digits: {{ object.last_four }}{% endif %}
{{ object.timestamp }}
90 | 91 | 92 | 93 |
94 |
95 | 96 | 97 | {% endblock %} -------------------------------------------------------------------------------- /src/templates/accounts/account_login_register.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load crispy_forms_tags %} 3 | 4 | 5 | {% block content %} 6 |
7 |
8 |

{{ title }}

9 |
10 |
11 | 12 | 13 |
{% csrf_token %} 14 | {{ form|crispy}} 15 | 16 |
17 | 18 | {% if extra_form_link %} 19 |
20 |

21 | {{ extra_form_link|safe }} 22 |

23 | {% endif %} 24 | 25 | 26 | {% if title == "Login" %} 27 |
28 |

29 | Need Account? Register now. 30 |

31 | {% endif %} 32 | 33 |
34 |
35 | 36 | 37 | {% endblock %} -------------------------------------------------------------------------------- /src/templates/accounts/home_logged_in.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Welcome | {% endblock %} 4 | 5 | 10 | 11 | 12 | 13 | 14 | {% block content %} 15 | 16 | {% if not recent_videos %} 17 |
18 |
19 | 20 |
21 |

Try these Videos

22 | {% for vid in random_videos %} 23 | {{ vid.title }}
24 | {% if forloop.counter == 1 %} 25 |
26 | {% endif %} 27 | {% endfor %} 28 |
29 |
30 | 31 | 32 |
33 |
34 | {% endif %} 35 | 36 |
37 |
38 |
39 | 40 | 41 |
42 |

Recent Videos

43 | {% if recent_videos %} 44 | 45 | {% for vid in recent_videos %} 46 | {{ vid.title }}
47 | {% if forloop.counter == 1 %} 48 |
49 | {% endif %} 50 | {% endfor %} 51 | 52 | 53 | 54 | {% else %} 55 |

56 | Recently viewed videos will be here. 57 |

58 | 59 | 60 | {% endif %} 61 |
62 |
63 | 64 | 65 | {% if popular_videos %} 66 |
67 |

Popular Videos

68 | {% for vid in popular_videos %} 69 | {{ vid.title }}
70 | {% if forloop.counter == 1 %} 71 |
72 | {% endif %} 73 | {% endfor %} 74 |
75 |
76 | 77 | {% endif %} 78 | 79 | 80 |
81 |
82 | 83 | 84 |
85 |

Recent Comments

86 | {% if recent_comments %} 87 | 88 | 89 | 90 | {% for comment in recent_comments %} 91 | 92 | {% endfor %} 93 | 94 |
{{ comment.get_preview }}
via {{ comment.user }}| {{ comment.timestamp|timesince }} ago
View
95 | {% else %} 96 | 97 |

98 | All comments will be here. 99 |

100 | {% endif %} 101 | 102 |
103 |
104 | 105 | {% endblock %} -------------------------------------------------------------------------------- /src/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | {% block title %}{% endblock %}srvup Project 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 106 | {% include 'extra_head_info.html' %} 107 | 108 | 109 | 110 | 111 | 112 | {% include 'navbar.html' %} 113 | 114 | {% block jumbotron %} 115 | {% endblock %} 116 | 117 |
118 | 119 | 120 |
121 | 122 |
123 | {% if messages %} 124 | {% for message in messages %} 125 | 127 | 128 | {% endfor %} 129 | {% endif %} 130 | {% block content %} 131 | {% endblock %} 132 | 133 | 142 | 143 |
144 | 145 | {% include 'footer.html' %} 146 | 147 | {% include 'javascript.html' %} 148 | 156 | 194 | 195 | 196 | 197 | -------------------------------------------------------------------------------- /src/templates/billing/history.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | 4 | {% block content %} 5 |
6 |
7 |

Billing History

8 | 9 |
10 |
11 | {% if not request.user.is_member %} 12 |
13 | Upgrade Account 14 | {% endif %} 15 |
16 |
17 | 18 |
19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | {% for object in queryset %} 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | {% endfor %} 39 | 40 |
Order #AmountPayment MethodDate
{{ object.order_id }}${{ object.amount }}{{ object.card_type }}{% if object.last_four %}
Last Four Digits: {{ object.last_four }}{% endif %}
{{ object.timestamp }}
41 |
42 |
43 | 44 | {% endblock %} -------------------------------------------------------------------------------- /src/templates/billing/upgrade.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | 20 | 21 | {% block content %} 22 |
23 |
24 | 25 |

Upgrade your account!

26 |

For $25/month get unlimited access

27 | 28 |
29 |
30 |
31 |
32 |
33 |
{% csrf_token %} 34 | 35 |
36 | 37 | 38 |
39 |
40 |
41 | {% endblock %} -------------------------------------------------------------------------------- /src/templates/comments/comment_thread.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load crispy_forms_tags %} 3 | 4 | {% block content %} 5 | 6 | Go Back 7 | 8 | 9 | 10 | 33 | 34 | 35 |
{{ comment.get_comment }} 11 |
via {{ comment.user }} | {{ comment.timestamp|timesince }} ago 12 | {% if not comment.is_child %} 13 |
    14 | {% for child in comment.get_children %} 15 |
  • {{ child.get_comment }} 16 | via {{ child.user }} 17 | 18 | 19 |
  • 20 | {% endfor %} 21 |
22 |
23 |
{% csrf_token %} 24 | 25 | {% crispy form form.helper %} 26 | 27 |
28 |
29 | {% endif %} 30 | 31 | 32 |
36 | 37 | {% endblock %} -------------------------------------------------------------------------------- /src/templates/company/contact_us.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | 4 | {% block content %} 5 | 6 |

Contact Us

7 | 8 | 9 | {% endblock %} -------------------------------------------------------------------------------- /src/templates/extra_head_info.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | 3 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/templates/footer.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |

© 2015 Srvup Project

6 | 7 |
8 | 9 |
-------------------------------------------------------------------------------- /src/templates/form.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | 4 | 5 | {% block content %} 6 | 7 |
{% csrf_token %} 8 | {{ form.as_p }} 9 | 10 |
11 | 12 | 13 | {% endblock %} -------------------------------------------------------------------------------- /src/templates/javascript.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/templates/navbar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 39 | -------------------------------------------------------------------------------- /src/templates/notifications/all.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | 4 | {% block content %} 5 | 6 |
7 | 8 |
9 |

Notifications

10 | 11 |
12 |
13 | 14 | 15 |
16 | 17 | {% if notifications %} 18 | 19 | 20 |
21 |
22 | 23 | {% for note in notifications %} 24 | 25 | 26 | 27 | {% endfor %} 28 |
{{ note|safe }}{% if note.read %} Read {% else %} Unread {% endif %}
29 | 30 |
31 |
32 | 33 | {% else %} 34 | 35 |

36 | This is where your notifications will appear once you have them. 37 |

38 | 39 | {% endif %} 40 | {% endblock %} -------------------------------------------------------------------------------- /src/templates/videos/category_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}{% endblock %} 4 | 5 | 6 | 7 | {% block content %} 8 | 9 | {{ queryset }} 10 | 11 | {% endblock %} -------------------------------------------------------------------------------- /src/templates/videos/category_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load future %} 3 | 4 | {% block title %}{% endblock %} 5 | 6 | 7 | 8 | {% block content %} 9 | 10 |
11 |
12 |

Categories

13 |
14 |
15 | 16 | 17 | 18 | 19 |
20 | 21 | {% for item in queryset %} 22 |
23 | 24 | {{ item }}
25 |
26 |
27 | 28 | {% cycle '' '

' %} 29 | 30 | {% if forloop.counter|divisibleby:10 %} 31 | 32 |
33 | 34 | {% endif %} 35 | 36 | 37 | {% endfor %} 38 | 39 |
40 | 41 | {% endblock %} -------------------------------------------------------------------------------- /src/templates/videos/video_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load crispy_forms_tags %} 3 | 4 | {% block title %}{% endblock %} 5 | 6 | 14 | 15 | 16 | 52 | 53 | 54 | 55 | 56 | {% block content %} 57 |
58 |
59 |

{{ obj.title }}

60 | 61 |
62 | 63 |
64 | 65 | {% if obj.get_previous_url %} 66 | 67 | {% endif %} 68 | {% if obj.get_next_url %} 69 | 70 | {% endif %} 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 |
83 | 84 | 85 |
86 |
87 |
88 | 89 |
90 |
91 | {{ obj.embed_code|safe }} 92 | 93 |
94 | 95 | {% if obj.tags.all %} 96 |
97 |
    98 | {% for tag in obj.tags.all %} 99 |
  • {{ tag }}
  • 100 | {% endfor %} 101 |
      102 |
103 | {% endif %} 104 | 105 |
106 | 107 | 108 | 109 |
110 |
111 | 112 |
113 |
114 |

Comments/Questions

115 |
{% csrf_token %} 116 | 117 | 118 | 119 | {% crispy comment_form comment_form.helper %} 120 |
121 | 122 | 123 |
124 |
125 | 126 | 127 | {% for comment in comments %} 128 | 129 | 155 | 156 | {% endfor %} 157 | 158 |
{{ comment.get_comment }} 130 |
via {{ comment.user }} | {{ comment.timestamp|timesince }} ago 131 | {% if not comment.is_child %} 132 |
    133 | {% for child in comment.get_children %} 134 |
  • {{ child.get_comment }} 135 | via {{ child.user }} 136 | 137 | 138 |
  • 139 | {% endfor %} 140 |
141 | View Thread | Reply 142 |
143 |
{% csrf_token %} 144 | 145 | 146 | 147 | {% crispy comment_form comment_form.helper %} 148 | 149 |
150 |
151 | {% endif %} 152 | 153 | 154 |
159 |
160 | 161 | 162 |
163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | {% endblock %} 174 | -------------------------------------------------------------------------------- /src/templates/videos/video_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}{% endblock %} 4 | 5 | 6 | 7 | {% block jumbotron %} 8 |
9 |
10 |
11 |
12 |
13 |

{{ obj.title }}

14 |
15 |
16 | 17 |
18 | 19 |
20 |
21 |
22 |
23 | {% endblock %} 24 | 25 | 26 | {% block content %} 27 | 28 | 29 | 30 | 31 |
32 | 33 | {% if queryset %} 34 | 35 | 36 | 37 | 38 | 39 | 40 | {% for item in queryset %} 41 | 42 | 43 | 44 | 45 | {% endfor %} 46 |
#Lecture
{{ forloop.counter }}{% if request.user.is_authenticated or item.has_preview %}{{ item }} {% else %} {{ item }} {% endif %}
47 | 48 | {% else %} 49 | 50 |

Project is coming soon.

51 | 52 | {% endif %} 53 |
54 | 55 | 56 | 57 | 58 | {% endblock %} -------------------------------------------------------------------------------- /src/videos/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/src/videos/__init__.py -------------------------------------------------------------------------------- /src/videos/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/src/videos/__init__.pyc -------------------------------------------------------------------------------- /src/videos/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.contenttypes.admin import GenericTabularInline 3 | # Register your models here. 4 | from .models import Video, Category, TaggedItem 5 | 6 | 7 | #admin.site.register(TaggedItem) 8 | 9 | class TaggedItemInline(GenericTabularInline): 10 | model = TaggedItem 11 | 12 | 13 | 14 | class VideoInline(admin.TabularInline): 15 | model = Video 16 | 17 | class VideoAdmin(admin.ModelAdmin): 18 | inlines = [TaggedItemInline] 19 | list_display = ["__unicode__", 'slug'] 20 | fields = ['title', 'order', 'share_message', 'embed_code','active','slug', 21 | 'featured', 'free_preview', 22 | 'category'] 23 | prepopulated_fields = { 24 | 'slug': ["title"], 25 | } 26 | class Meta: 27 | model = Video 28 | 29 | admin.site.register(Video, VideoAdmin) 30 | 31 | 32 | class CategoryAdmin(admin.ModelAdmin): 33 | inlines = [VideoInline, TaggedItemInline] 34 | class Meta: 35 | model = Category 36 | 37 | admin.site.register(Category, CategoryAdmin) 38 | -------------------------------------------------------------------------------- /src/videos/admin.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/src/videos/admin.pyc -------------------------------------------------------------------------------- /src/videos/forms.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/src/videos/forms.py -------------------------------------------------------------------------------- /src/videos/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='Video', 15 | fields=[ 16 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 17 | ('title', models.CharField(max_length=120)), 18 | ('embed_code', models.CharField(max_length=500, null=True, blank=True)), 19 | ], 20 | options={ 21 | }, 22 | bases=(models.Model,), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /src/videos/migrations/0001_initial.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/src/videos/migrations/0001_initial.pyc -------------------------------------------------------------------------------- /src/videos/migrations/0002_auto_20150116_2215.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('videos', '0001_initial'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='video', 16 | name='active', 17 | field=models.BooleanField(default=True), 18 | preserve_default=True, 19 | ), 20 | migrations.AddField( 21 | model_name='video', 22 | name='featured', 23 | field=models.BooleanField(default=False), 24 | preserve_default=True, 25 | ), 26 | migrations.AddField( 27 | model_name='video', 28 | name='free_preview', 29 | field=models.BooleanField(default=False), 30 | preserve_default=True, 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /src/videos/migrations/0003_auto_20150117_0006.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('videos', '0002_auto_20150116_2215'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Category', 16 | fields=[ 17 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 18 | ('title', models.CharField(max_length=120)), 19 | ('description', models.TextField(max_length=5000, null=True, blank=True)), 20 | ('image', models.ImageField(null=True, upload_to=b'images/', blank=True)), 21 | ('active', models.BooleanField(default=True)), 22 | ('featured', models.BooleanField(default=False)), 23 | ('timestamp', models.DateTimeField(auto_now_add=True)), 24 | ('updated', models.DateTimeField(auto_now=True)), 25 | ], 26 | options={ 27 | }, 28 | bases=(models.Model,), 29 | ), 30 | migrations.AddField( 31 | model_name='video', 32 | name='category', 33 | field=models.ForeignKey(to='videos.Category', null=True), 34 | preserve_default=True, 35 | ), 36 | migrations.AddField( 37 | model_name='video', 38 | name='timestamp', 39 | field=models.DateTimeField(auto_now_add=True, null=True), 40 | preserve_default=True, 41 | ), 42 | migrations.AddField( 43 | model_name='video', 44 | name='updated', 45 | field=models.DateTimeField(auto_now=True, null=True), 46 | preserve_default=True, 47 | ), 48 | ] 49 | -------------------------------------------------------------------------------- /src/videos/migrations/0004_auto_20150117_0013.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('videos', '0003_auto_20150117_0006'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='video', 16 | name='category', 17 | ), 18 | migrations.AddField( 19 | model_name='category', 20 | name='videos', 21 | field=models.ManyToManyField(to='videos.Video', null=True, blank=True), 22 | preserve_default=True, 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /src/videos/migrations/0005_auto_20150117_0021.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('videos', '0004_auto_20150117_0013'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='category', 16 | name='videos', 17 | ), 18 | migrations.AddField( 19 | model_name='video', 20 | name='category', 21 | field=models.ForeignKey(to='videos.Category', null=True), 22 | preserve_default=True, 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /src/videos/migrations/0006_category_slug.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('videos', '0005_auto_20150117_0021'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='category', 16 | name='slug', 17 | field=models.SlugField(default=b'abc', unique=True), 18 | preserve_default=True, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/videos/migrations/0007_auto_20150117_0048.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('videos', '0006_category_slug'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='video', 16 | name='category', 17 | field=models.ForeignKey(default=1, to='videos.Category'), 18 | preserve_default=True, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/videos/migrations/0008_auto_20150119_2127.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('videos', '0007_auto_20150117_0048'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='video', 16 | name='slug', 17 | field=models.SlugField(null=True, blank=True), 18 | preserve_default=True, 19 | ), 20 | migrations.AlterUniqueTogether( 21 | name='video', 22 | unique_together=set([('slug', 'category')]), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /src/videos/migrations/0009_video_share_message.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('videos', '0008_auto_20150119_2127'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='video', 16 | name='share_message', 17 | field=models.TextField(default=b'\nCheck out this awesome video.\n'), 18 | preserve_default=True, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/videos/migrations/0010_auto_20150120_2335.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('videos', '0009_video_share_message'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='video', 16 | name='share_message', 17 | field=models.TextField(default=b'Check out this awesome video.'), 18 | preserve_default=True, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/videos/migrations/0011_taggeditem.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('contenttypes', '0001_initial'), 11 | ('videos', '0010_auto_20150120_2335'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='TaggedItem', 17 | fields=[ 18 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 19 | ('tag', models.SlugField(choices=[(b'python', b'python'), (b'django', b'django')])), 20 | ('object_id', models.PositiveIntegerField()), 21 | ('content_type', models.ForeignKey(to='contenttypes.ContentType')), 22 | ], 23 | options={ 24 | }, 25 | bases=(models.Model,), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /src/videos/migrations/0012_auto_20150126_0712.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('videos', '0011_taggeditem'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='taggeditem', 16 | name='tag', 17 | field=models.SlugField(choices=[(b'python', b'python'), (b'django', b'django'), (b'css', b'css'), (b'bootstrap', b'bootstrap')]), 18 | preserve_default=True, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/videos/migrations/0013_auto_20150130_2131.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('videos', '0012_auto_20150126_0712'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterModelOptions( 15 | name='category', 16 | options={'ordering': ['title', 'timestamp']}, 17 | ), 18 | migrations.AddField( 19 | model_name='video', 20 | name='order', 21 | field=models.PositiveIntegerField(default=1), 22 | preserve_default=True, 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /src/videos/migrations/0014_auto_20150131_1915.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('videos', '0013_auto_20150130_2131'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterModelOptions( 15 | name='video', 16 | options={'ordering': ['order', '-timestamp']}, 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /src/videos/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/src/videos/migrations/__init__.py -------------------------------------------------------------------------------- /src/videos/migrations/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/src/videos/migrations/__init__.pyc -------------------------------------------------------------------------------- /src/videos/models.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/src/videos/models.pyc -------------------------------------------------------------------------------- /src/videos/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /src/videos/utils.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | def get_vid_for_direction(instance, direction): 4 | ''' get next video instance based on direction and current video instance''' 5 | category = instance.category 6 | video_qs = category.video_set.all() 7 | if direction == "next": 8 | new_qs = video_qs.filter(order__gt=instance.order) 9 | else: 10 | new_qs = video_qs.filter(order__lt=instance.order).reverse() 11 | next_vid = None 12 | if len(new_qs) >= 1: 13 | try: 14 | next_vid = new_qs[0] 15 | except IndexError: 16 | next_vid = None 17 | return next_vid 18 | 19 | -------------------------------------------------------------------------------- /src/videos/views.py: -------------------------------------------------------------------------------- 1 | from itertools import chain 2 | 3 | from django.core.urlresolvers import reverse 4 | from django.contrib.auth.decorators import login_required 5 | from django.contrib.contenttypes.models import ContentType 6 | from django.shortcuts import render, Http404, HttpResponseRedirect, get_object_or_404 7 | 8 | # Create your views here. 9 | from analytics.signals import page_view 10 | from comments.forms import CommentForm 11 | from comments.models import Comment 12 | 13 | 14 | 15 | from .models import Video, Category, TaggedItem 16 | 17 | 18 | #@login_required 19 | def video_detail(request, cat_slug, vid_slug): 20 | cat = get_object_or_404(Category, slug=cat_slug) 21 | obj = get_object_or_404(Video, slug=vid_slug, category=cat) 22 | page_view.send(request.user, 23 | page_path=request.get_full_path(), 24 | primary_obj=obj, 25 | secondary_obj=cat) 26 | if request.user.is_authenticated() or obj.has_preview: 27 | try: 28 | is_member = request.user.is_member 29 | except: 30 | is_member = None 31 | if is_member or obj.has_preview: 32 | comments = obj.comment_set.all() 33 | for c in comments: 34 | c.get_children() 35 | comment_form = CommentForm() 36 | context = {"obj": obj, 37 | "comments":comments, 38 | "comment_form": comment_form} 39 | return render(request, "videos/video_detail.html", context) 40 | else: 41 | # upgrade account 42 | next_url = obj.get_absolute_url() 43 | return HttpResponseRedirect("%s?next=%s"%(reverse('account_upgrade'), next_url)) 44 | else: 45 | next_url = obj.get_absolute_url() 46 | return HttpResponseRedirect("%s?next=%s"%(reverse('login'), next_url)) 47 | 48 | 49 | 50 | def category_list(request): 51 | queryset = Category.objects.all() 52 | # queryset2 = Category.objects.all() 53 | # queryset3 = list(chain(queryset,queryset2)) 54 | context = { 55 | "queryset": queryset, 56 | } 57 | return render(request, "videos/category_list.html", context) 58 | 59 | 60 | 61 | # @login_required 62 | def category_detail(request, cat_slug): 63 | obj = get_object_or_404(Category, slug=cat_slug) 64 | queryset = obj.video_set.all() 65 | page_view.send(request.user, 66 | page_path=request.get_full_path(), 67 | primary_obj=obj) 68 | 69 | 70 | print queryset 71 | return render(request, "videos/video_list.html", {"obj": obj, "queryset": queryset}) 72 | 73 | 74 | 75 | # def video_edit(request): 76 | 77 | # return render(request, "videos/video_single.html", {}) 78 | 79 | 80 | # def video_create(request): 81 | 82 | # return render(request, "videos/video_single.html", {}) 83 | -------------------------------------------------------------------------------- /srvup.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "follow_symlinks": true, 6 | "path": "src" 7 | }, 8 | { 9 | "follow_symlinks": true, 10 | "path": "static" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /static/media/images/Screen_Shot_2015-01-30_at_1.07.12_PM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/media/images/Screen_Shot_2015-01-30_at_1.07.12_PM.png -------------------------------------------------------------------------------- /static/media/images/djangogap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/media/images/djangogap.png -------------------------------------------------------------------------------- /static/media/images/heroku_django.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/media/images/heroku_django.png -------------------------------------------------------------------------------- /static/media/images/launch_with_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/media/images/launch_with_code.png -------------------------------------------------------------------------------- /static/media/images/open-ecommerce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/media/images/open-ecommerce.png -------------------------------------------------------------------------------- /static/media/images/srvup_membership.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/media/images/srvup_membership.png -------------------------------------------------------------------------------- /static/media/images/try_django_17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/media/images/try_django_17.jpg -------------------------------------------------------------------------------- /static/static_dirs/js/ie-emulation-modes-warning.js: -------------------------------------------------------------------------------- 1 | // NOTICE!! DO NOT USE ANY OF THIS JAVASCRIPT 2 | // IT'S JUST JUNK FOR OUR DOCS! 3 | // ++++++++++++++++++++++++++++++++++++++++++ 4 | /*! 5 | * Copyright 2014 Twitter, Inc. 6 | * 7 | * Licensed under the Creative Commons Attribution 3.0 Unported License. For 8 | * details, see http://creativecommons.org/licenses/by/3.0/. 9 | */ 10 | // Intended to prevent false-positive bug reports about Bootstrap not working properly in old versions of IE due to folks testing using IE's unreliable emulation modes. 11 | (function () { 12 | 'use strict'; 13 | 14 | function emulatedIEMajorVersion() { 15 | var groups = /MSIE ([0-9.]+)/.exec(window.navigator.userAgent) 16 | if (groups === null) { 17 | return null 18 | } 19 | var ieVersionNum = parseInt(groups[1], 10) 20 | var ieMajorVersion = Math.floor(ieVersionNum) 21 | return ieMajorVersion 22 | } 23 | 24 | function actualNonEmulatedIEMajorVersion() { 25 | // Detects the actual version of IE in use, even if it's in an older-IE emulation mode. 26 | // IE JavaScript conditional compilation docs: http://msdn.microsoft.com/en-us/library/ie/121hztk3(v=vs.94).aspx 27 | // @cc_on docs: http://msdn.microsoft.com/en-us/library/ie/8ka90k2e(v=vs.94).aspx 28 | var jscriptVersion = new Function('/*@cc_on return @_jscript_version; @*/')() // jshint ignore:line 29 | if (jscriptVersion === undefined) { 30 | return 11 // IE11+ not in emulation mode 31 | } 32 | if (jscriptVersion < 9) { 33 | return 8 // IE8 (or lower; haven't tested on IE<8) 34 | } 35 | return jscriptVersion // IE9 or IE10 in any mode, or IE11 in non-IE11 mode 36 | } 37 | 38 | var ua = window.navigator.userAgent 39 | if (ua.indexOf('Opera') > -1 || ua.indexOf('Presto') > -1) { 40 | return // Opera, which might pretend to be IE 41 | } 42 | var emulated = emulatedIEMajorVersion() 43 | if (emulated === null) { 44 | return // Not IE 45 | } 46 | var nonEmulated = actualNonEmulatedIEMajorVersion() 47 | 48 | if (emulated !== nonEmulated) { 49 | window.alert('WARNING: You appear to be using IE' + nonEmulated + ' in IE' + emulated + ' emulation mode.\nIE emulation modes can behave significantly differently from ACTUAL older versions of IE.\nPLEASE DON\'T FILE BOOTSTRAP BUGS based on testing in IE emulation modes!') 50 | } 51 | })(); 52 | -------------------------------------------------------------------------------- /static/static_dirs/js/ie10-viewport-bug-workaround.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * IE10 viewport hack for Surface/desktop Windows 8 bug 3 | * Copyright 2014 Twitter, Inc. 4 | * Licensed under the Creative Commons Attribution 3.0 Unported License. For 5 | * details, see http://creativecommons.org/licenses/by/3.0/. 6 | */ 7 | 8 | // See the Getting Started docs for more information: 9 | // http://getbootstrap.com/getting-started/#support-ie10-width 10 | 11 | (function () { 12 | 'use strict'; 13 | if (navigator.userAgent.match(/IEMobile\/10\.0/)) { 14 | var msViewportStyle = document.createElement('style') 15 | msViewportStyle.appendChild( 16 | document.createTextNode( 17 | '@-ms-viewport{width:auto!important}' 18 | ) 19 | ) 20 | document.querySelector('head').appendChild(msViewportStyle) 21 | } 22 | })(); 23 | -------------------------------------------------------------------------------- /static/static_root/admin/css/dashboard.css: -------------------------------------------------------------------------------- 1 | /* DASHBOARD */ 2 | 3 | .dashboard .module table th { 4 | width: 100%; 5 | } 6 | 7 | .dashboard .module table td { 8 | white-space: nowrap; 9 | } 10 | 11 | .dashboard .module table td a { 12 | display: block; 13 | padding-right: .6em; 14 | } 15 | 16 | /* RECENT ACTIONS MODULE */ 17 | 18 | .module ul.actionlist { 19 | margin-left: 0; 20 | } 21 | 22 | ul.actionlist li { 23 | list-style-type: none; 24 | } 25 | 26 | ul.actionlist li { 27 | overflow: hidden; 28 | text-overflow: ellipsis; 29 | -o-text-overflow: ellipsis; 30 | } 31 | -------------------------------------------------------------------------------- /static/static_root/admin/css/ie.css: -------------------------------------------------------------------------------- 1 | /* IE 6 & 7 */ 2 | 3 | /* Proper fixed width for dashboard in IE6 */ 4 | 5 | .dashboard #content { 6 | *width: 768px; 7 | } 8 | 9 | .dashboard #content-main { 10 | *width: 535px; 11 | } 12 | 13 | /* IE 6 ONLY */ 14 | 15 | /* Keep header from flowing off the page */ 16 | 17 | #container { 18 | _position: static; 19 | } 20 | 21 | /* Put the right sidebars back on the page */ 22 | 23 | .colMS #content-related { 24 | _margin-right: 0; 25 | _margin-left: 10px; 26 | _position: static; 27 | } 28 | 29 | /* Put the left sidebars back on the page */ 30 | 31 | .colSM #content-related { 32 | _margin-right: 10px; 33 | _margin-left: -115px; 34 | _position: static; 35 | } 36 | 37 | .form-row { 38 | _height: 1%; 39 | } 40 | 41 | /* Fix right margin for changelist filters in IE6 */ 42 | 43 | #changelist-filter ul { 44 | _margin-right: -10px; 45 | } 46 | 47 | /* IE ignores min-height, but treats height as if it were min-height */ 48 | 49 | .change-list .filtered { 50 | _height: 400px; 51 | } 52 | 53 | /* IE doesn't know alpha transparency in PNGs */ 54 | 55 | .inline-deletelink { 56 | background: transparent url(../img/inline-delete-8bit.png) no-repeat; 57 | } 58 | 59 | /* IE7 doesn't support inline-block */ 60 | .change-list ul.toplinks li { 61 | zoom: 1; 62 | *display: inline; 63 | } -------------------------------------------------------------------------------- /static/static_root/admin/css/login.css: -------------------------------------------------------------------------------- 1 | /* LOGIN FORM */ 2 | 3 | body.login { 4 | background: #eee; 5 | } 6 | 7 | .login #container { 8 | background: white; 9 | border: 1px solid #ccc; 10 | width: 28em; 11 | min-width: 300px; 12 | margin-left: auto; 13 | margin-right: auto; 14 | margin-top: 100px; 15 | } 16 | 17 | .login #content-main { 18 | width: 100%; 19 | } 20 | 21 | .login form { 22 | margin-top: 1em; 23 | } 24 | 25 | .login .form-row { 26 | padding: 4px 0; 27 | float: left; 28 | width: 100%; 29 | } 30 | 31 | .login .form-row label { 32 | padding-right: 0.5em; 33 | line-height: 2em; 34 | font-size: 1em; 35 | clear: both; 36 | color: #333; 37 | } 38 | 39 | .login .form-row #id_username, .login .form-row #id_password { 40 | clear: both; 41 | padding: 6px; 42 | width: 100%; 43 | -webkit-box-sizing: border-box; 44 | -moz-box-sizing: border-box; 45 | box-sizing: border-box; 46 | } 47 | 48 | .login span.help { 49 | font-size: 10px; 50 | display: block; 51 | } 52 | 53 | .login .submit-row { 54 | clear: both; 55 | padding: 1em 0 0 9.4em; 56 | } 57 | 58 | .login .password-reset-link { 59 | text-align: center; 60 | } 61 | -------------------------------------------------------------------------------- /static/static_root/admin/css/rtl.css: -------------------------------------------------------------------------------- 1 | body { 2 | direction: rtl; 3 | } 4 | 5 | /* LOGIN */ 6 | 7 | .login .form-row { 8 | float: right; 9 | } 10 | 11 | .login .form-row label { 12 | float: right; 13 | padding-left: 0.5em; 14 | padding-right: 0; 15 | text-align: left; 16 | } 17 | 18 | .login .submit-row { 19 | clear: both; 20 | padding: 1em 9.4em 0 0; 21 | } 22 | 23 | /* GLOBAL */ 24 | 25 | th { 26 | text-align: right; 27 | } 28 | 29 | .module h2, .module caption { 30 | text-align: right; 31 | } 32 | 33 | .addlink, .changelink { 34 | padding-left: 0px; 35 | padding-right: 12px; 36 | background-position: 100% 0.2em; 37 | } 38 | 39 | .deletelink { 40 | padding-left: 0px; 41 | padding-right: 12px; 42 | background-position: 100% 0.25em; 43 | } 44 | 45 | .object-tools { 46 | float: left; 47 | } 48 | 49 | thead th:first-child, 50 | tfoot td:first-child { 51 | border-left: 1px solid #ddd !important; 52 | } 53 | 54 | /* LAYOUT */ 55 | 56 | #user-tools { 57 | right: auto; 58 | left: 0; 59 | text-align: left; 60 | } 61 | 62 | div.breadcrumbs { 63 | text-align: right; 64 | } 65 | 66 | #content-main { 67 | float: right; 68 | } 69 | 70 | #content-related { 71 | float: left; 72 | margin-left: -19em; 73 | margin-right: auto; 74 | } 75 | 76 | .colMS { 77 | margin-left: 20em !important; 78 | margin-right: 10px !important; 79 | } 80 | 81 | /* SORTABLE TABLES */ 82 | 83 | table thead th.sorted .sortoptions { 84 | float: left; 85 | } 86 | 87 | thead th.sorted .text { 88 | padding-right: 0; 89 | padding-left: 42px; 90 | } 91 | 92 | /* dashboard styles */ 93 | 94 | .dashboard .module table td a { 95 | padding-left: .6em; 96 | padding-right: 12px; 97 | } 98 | 99 | /* changelists styles */ 100 | 101 | .change-list .filtered { 102 | background: white url(../img/changelist-bg_rtl.gif) top left repeat-y !important; 103 | } 104 | 105 | .change-list .filtered table { 106 | border-left: 1px solid #ddd; 107 | border-right: 0px none; 108 | } 109 | 110 | #changelist-filter { 111 | right: auto; 112 | left: 0; 113 | border-left: 0px none; 114 | border-right: 1px solid #ddd; 115 | } 116 | 117 | .change-list .filtered .results, .change-list .filtered .paginator, .filtered #toolbar, .filtered div.xfull { 118 | margin-right: 0px !important; 119 | margin-left: 160px !important; 120 | } 121 | 122 | #changelist-filter li.selected { 123 | border-left: 0px none; 124 | padding-left: 0px; 125 | margin-left: 0; 126 | border-right: 5px solid #ccc; 127 | padding-right: 5px; 128 | margin-right: -10px; 129 | } 130 | 131 | .filtered .actions { 132 | border-left:1px solid #DDDDDD; 133 | margin-left:160px !important; 134 | border-right: 0 none; 135 | margin-right:0 !important; 136 | } 137 | 138 | #changelist table tbody td:first-child, #changelist table tbody th:first-child { 139 | border-right: 0; 140 | border-left: 1px solid #ddd; 141 | } 142 | 143 | /* FORMS */ 144 | 145 | .aligned label { 146 | padding: 0 0 3px 1em; 147 | float: right; 148 | } 149 | 150 | .submit-row { 151 | text-align: left 152 | } 153 | 154 | .submit-row p.deletelink-box { 155 | float: right; 156 | } 157 | 158 | .submit-row .deletelink { 159 | background: url(../img/icon_deletelink.gif) 0 50% no-repeat; 160 | padding-right: 14px; 161 | } 162 | 163 | .vDateField, .vTimeField { 164 | margin-left: 2px; 165 | } 166 | 167 | form ul.inline li { 168 | float: right; 169 | padding-right: 0; 170 | padding-left: 7px; 171 | } 172 | 173 | input[type=submit].default, .submit-row input.default { 174 | float: left; 175 | } 176 | 177 | fieldset .field-box { 178 | float: right; 179 | margin-left: 20px; 180 | margin-right: 0; 181 | } 182 | 183 | .errorlist li { 184 | background-position: 100% .3em; 185 | padding: 4px 25px 4px 5px; 186 | } 187 | 188 | .errornote { 189 | background-position: 100% .3em; 190 | padding: 4px 25px 4px 5px; 191 | } 192 | 193 | /* WIDGETS */ 194 | 195 | .calendarnav-previous { 196 | top: 0; 197 | left: auto; 198 | right: 0; 199 | } 200 | 201 | .calendarnav-next { 202 | top: 0; 203 | right: auto; 204 | left: 0; 205 | } 206 | 207 | .calendar caption, .calendarbox h2 { 208 | text-align: center; 209 | } 210 | 211 | .selector { 212 | float: right; 213 | } 214 | 215 | .selector .selector-filter { 216 | text-align: right; 217 | } 218 | 219 | .inline-deletelink { 220 | float: left; 221 | } 222 | 223 | /* MISC */ 224 | 225 | .inline-related h2, .inline-group h2 { 226 | text-align: right 227 | } 228 | 229 | .inline-related h3 span.delete { 230 | padding-right: 20px; 231 | padding-left: inherit; 232 | left: 10px; 233 | right: inherit; 234 | float:left; 235 | } 236 | 237 | .inline-related h3 span.delete label { 238 | margin-left: inherit; 239 | margin-right: 2px; 240 | } 241 | 242 | /* IE7 specific bug fixes */ 243 | 244 | div.colM { 245 | position: relative; 246 | } 247 | 248 | .submit-row input { 249 | float: left; 250 | } -------------------------------------------------------------------------------- /static/static_root/admin/img/changelist-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/changelist-bg.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/changelist-bg_rtl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/changelist-bg_rtl.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/default-bg-reverse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/default-bg-reverse.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/default-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/default-bg.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/deleted-overlay.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/deleted-overlay.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/gis/move_vertex_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/gis/move_vertex_off.png -------------------------------------------------------------------------------- /static/static_root/admin/img/gis/move_vertex_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/gis/move_vertex_on.png -------------------------------------------------------------------------------- /static/static_root/admin/img/icon-no.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/icon-no.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/icon-unknown.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/icon-unknown.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/icon-yes.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/icon-yes.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/icon_addlink.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/icon_addlink.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/icon_alert.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/icon_alert.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/icon_calendar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/icon_calendar.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/icon_changelink.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/icon_changelink.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/icon_clock.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/icon_clock.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/icon_deletelink.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/icon_deletelink.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/icon_error.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/icon_error.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/icon_searchbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/icon_searchbox.png -------------------------------------------------------------------------------- /static/static_root/admin/img/icon_success.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/icon_success.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/inline-delete-8bit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/inline-delete-8bit.png -------------------------------------------------------------------------------- /static/static_root/admin/img/inline-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/inline-delete.png -------------------------------------------------------------------------------- /static/static_root/admin/img/inline-restore-8bit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/inline-restore-8bit.png -------------------------------------------------------------------------------- /static/static_root/admin/img/inline-restore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/inline-restore.png -------------------------------------------------------------------------------- /static/static_root/admin/img/inline-splitter-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/inline-splitter-bg.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/nav-bg-grabber.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/nav-bg-grabber.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/nav-bg-reverse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/nav-bg-reverse.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/nav-bg-selected.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/nav-bg-selected.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/nav-bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/nav-bg.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/selector-icons.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/selector-icons.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/selector-search.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/selector-search.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/sorting-icons.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/sorting-icons.gif -------------------------------------------------------------------------------- /static/static_root/admin/img/tooltag-add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/tooltag-add.png -------------------------------------------------------------------------------- /static/static_root/admin/img/tooltag-arrowright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingforentrepreneurs/srvup-membership/4f6279849668225390fdb94255a5612cb28739be/static/static_root/admin/img/tooltag-arrowright.png -------------------------------------------------------------------------------- /static/static_root/admin/js/LICENSE-JQUERY.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 John Resig, http://jquery.com/ 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /static/static_root/admin/js/SelectBox.js: -------------------------------------------------------------------------------- 1 | var SelectBox = { 2 | cache: new Object(), 3 | init: function(id) { 4 | var box = document.getElementById(id); 5 | var node; 6 | SelectBox.cache[id] = new Array(); 7 | var cache = SelectBox.cache[id]; 8 | for (var i = 0; (node = box.options[i]); i++) { 9 | cache.push({value: node.value, text: node.text, displayed: 1}); 10 | } 11 | }, 12 | redisplay: function(id) { 13 | // Repopulate HTML select box from cache 14 | var box = document.getElementById(id); 15 | box.options.length = 0; // clear all options 16 | for (var i = 0, j = SelectBox.cache[id].length; i < j; i++) { 17 | var node = SelectBox.cache[id][i]; 18 | if (node.displayed) { 19 | var new_option = new Option(node.text, node.value, false, false); 20 | // Shows a tooltip when hovering over the option 21 | new_option.setAttribute("title", node.text); 22 | box.options[box.options.length] = new_option; 23 | } 24 | } 25 | }, 26 | filter: function(id, text) { 27 | // Redisplay the HTML select box, displaying only the choices containing ALL 28 | // the words in text. (It's an AND search.) 29 | var tokens = text.toLowerCase().split(/\s+/); 30 | var node, token; 31 | for (var i = 0; (node = SelectBox.cache[id][i]); i++) { 32 | node.displayed = 1; 33 | for (var j = 0; (token = tokens[j]); j++) { 34 | if (node.text.toLowerCase().indexOf(token) == -1) { 35 | node.displayed = 0; 36 | } 37 | } 38 | } 39 | SelectBox.redisplay(id); 40 | }, 41 | delete_from_cache: function(id, value) { 42 | var node, delete_index = null; 43 | for (var i = 0; (node = SelectBox.cache[id][i]); i++) { 44 | if (node.value == value) { 45 | delete_index = i; 46 | break; 47 | } 48 | } 49 | var j = SelectBox.cache[id].length - 1; 50 | for (var i = delete_index; i < j; i++) { 51 | SelectBox.cache[id][i] = SelectBox.cache[id][i+1]; 52 | } 53 | SelectBox.cache[id].length--; 54 | }, 55 | add_to_cache: function(id, option) { 56 | SelectBox.cache[id].push({value: option.value, text: option.text, displayed: 1}); 57 | }, 58 | cache_contains: function(id, value) { 59 | // Check if an item is contained in the cache 60 | var node; 61 | for (var i = 0; (node = SelectBox.cache[id][i]); i++) { 62 | if (node.value == value) { 63 | return true; 64 | } 65 | } 66 | return false; 67 | }, 68 | move: function(from, to) { 69 | var from_box = document.getElementById(from); 70 | var to_box = document.getElementById(to); 71 | var option; 72 | for (var i = 0; (option = from_box.options[i]); i++) { 73 | if (option.selected && SelectBox.cache_contains(from, option.value)) { 74 | SelectBox.add_to_cache(to, {value: option.value, text: option.text, displayed: 1}); 75 | SelectBox.delete_from_cache(from, option.value); 76 | } 77 | } 78 | SelectBox.redisplay(from); 79 | SelectBox.redisplay(to); 80 | }, 81 | move_all: function(from, to) { 82 | var from_box = document.getElementById(from); 83 | var to_box = document.getElementById(to); 84 | var option; 85 | for (var i = 0; (option = from_box.options[i]); i++) { 86 | if (SelectBox.cache_contains(from, option.value)) { 87 | SelectBox.add_to_cache(to, {value: option.value, text: option.text, displayed: 1}); 88 | SelectBox.delete_from_cache(from, option.value); 89 | } 90 | } 91 | SelectBox.redisplay(from); 92 | SelectBox.redisplay(to); 93 | }, 94 | sort: function(id) { 95 | SelectBox.cache[id].sort( function(a, b) { 96 | a = a.text.toLowerCase(); 97 | b = b.text.toLowerCase(); 98 | try { 99 | if (a > b) return 1; 100 | if (a < b) return -1; 101 | } 102 | catch (e) { 103 | // silently fail on IE 'unknown' exception 104 | } 105 | return 0; 106 | } ); 107 | }, 108 | select_all: function(id) { 109 | var box = document.getElementById(id); 110 | for (var i = 0; i < box.options.length; i++) { 111 | box.options[i].selected = 'selected'; 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /static/static_root/admin/js/actions.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | var lastChecked; 3 | 4 | $.fn.actions = function(opts) { 5 | var options = $.extend({}, $.fn.actions.defaults, opts); 6 | var actionCheckboxes = $(this); 7 | var list_editable_changed = false; 8 | var checker = function(checked) { 9 | if (checked) { 10 | showQuestion(); 11 | } else { 12 | reset(); 13 | } 14 | $(actionCheckboxes).prop("checked", checked) 15 | .parent().parent().toggleClass(options.selectedClass, checked); 16 | }, 17 | updateCounter = function() { 18 | var sel = $(actionCheckboxes).filter(":checked").length; 19 | // _actions_icnt is defined in the generated HTML 20 | // and contains the total amount of objects in the queryset 21 | $(options.counterContainer).html(interpolate( 22 | ngettext('%(sel)s of %(cnt)s selected', '%(sel)s of %(cnt)s selected', sel), { 23 | sel: sel, 24 | cnt: _actions_icnt 25 | }, true)); 26 | $(options.allToggle).prop("checked", function() { 27 | var value; 28 | if (sel == actionCheckboxes.length) { 29 | value = true; 30 | showQuestion(); 31 | } else { 32 | value = false; 33 | clearAcross(); 34 | } 35 | return value; 36 | }); 37 | }, 38 | showQuestion = function() { 39 | $(options.acrossClears).hide(); 40 | $(options.acrossQuestions).show(); 41 | $(options.allContainer).hide(); 42 | }, 43 | showClear = function() { 44 | $(options.acrossClears).show(); 45 | $(options.acrossQuestions).hide(); 46 | $(options.actionContainer).toggleClass(options.selectedClass); 47 | $(options.allContainer).show(); 48 | $(options.counterContainer).hide(); 49 | }, 50 | reset = function() { 51 | $(options.acrossClears).hide(); 52 | $(options.acrossQuestions).hide(); 53 | $(options.allContainer).hide(); 54 | $(options.counterContainer).show(); 55 | }, 56 | clearAcross = function() { 57 | reset(); 58 | $(options.acrossInput).val(0); 59 | $(options.actionContainer).removeClass(options.selectedClass); 60 | }; 61 | // Show counter by default 62 | $(options.counterContainer).show(); 63 | // Check state of checkboxes and reinit state if needed 64 | $(this).filter(":checked").each(function(i) { 65 | $(this).parent().parent().toggleClass(options.selectedClass); 66 | updateCounter(); 67 | if ($(options.acrossInput).val() == 1) { 68 | showClear(); 69 | } 70 | }); 71 | $(options.allToggle).show().click(function() { 72 | checker($(this).prop("checked")); 73 | updateCounter(); 74 | }); 75 | $("a", options.acrossQuestions).click(function(event) { 76 | event.preventDefault(); 77 | $(options.acrossInput).val(1); 78 | showClear(); 79 | }); 80 | $("a", options.acrossClears).click(function(event) { 81 | event.preventDefault(); 82 | $(options.allToggle).prop("checked", false); 83 | clearAcross(); 84 | checker(0); 85 | updateCounter(); 86 | }); 87 | lastChecked = null; 88 | $(actionCheckboxes).click(function(event) { 89 | if (!event) { event = window.event; } 90 | var target = event.target ? event.target : event.srcElement; 91 | if (lastChecked && $.data(lastChecked) != $.data(target) && event.shiftKey === true) { 92 | var inrange = false; 93 | $(lastChecked).prop("checked", target.checked) 94 | .parent().parent().toggleClass(options.selectedClass, target.checked); 95 | $(actionCheckboxes).each(function() { 96 | if ($.data(this) == $.data(lastChecked) || $.data(this) == $.data(target)) { 97 | inrange = (inrange) ? false : true; 98 | } 99 | if (inrange) { 100 | $(this).prop("checked", target.checked) 101 | .parent().parent().toggleClass(options.selectedClass, target.checked); 102 | } 103 | }); 104 | } 105 | $(target).parent().parent().toggleClass(options.selectedClass, target.checked); 106 | lastChecked = target; 107 | updateCounter(); 108 | }); 109 | $('form#changelist-form table#result_list tr').find('td:gt(0) :input').change(function() { 110 | list_editable_changed = true; 111 | }); 112 | $('form#changelist-form button[name="index"]').click(function(event) { 113 | if (list_editable_changed) { 114 | return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost.")); 115 | } 116 | }); 117 | $('form#changelist-form input[name="_save"]').click(function(event) { 118 | var action_changed = false; 119 | $('select option:selected', options.actionContainer).each(function() { 120 | if ($(this).val()) { 121 | action_changed = true; 122 | } 123 | }); 124 | if (action_changed) { 125 | if (list_editable_changed) { 126 | return confirm(gettext("You have selected an action, but you haven't saved your changes to individual fields yet. Please click OK to save. You'll need to re-run the action.")); 127 | } else { 128 | return confirm(gettext("You have selected an action, and you haven't made any changes on individual fields. You're probably looking for the Go button rather than the Save button.")); 129 | } 130 | } 131 | }); 132 | }; 133 | /* Setup plugin defaults */ 134 | $.fn.actions.defaults = { 135 | actionContainer: "div.actions", 136 | counterContainer: "span.action-counter", 137 | allContainer: "div.actions span.all", 138 | acrossInput: "div.actions input.select-across", 139 | acrossQuestions: "div.actions span.question", 140 | acrossClears: "div.actions span.clear", 141 | allToggle: "#action-toggle", 142 | selectedClass: "selected" 143 | }; 144 | })(django.jQuery); 145 | -------------------------------------------------------------------------------- /static/static_root/admin/js/actions.min.js: -------------------------------------------------------------------------------- 1 | (function(a){var f;a.fn.actions=function(q){var b=a.extend({},a.fn.actions.defaults,q),g=a(this),e=!1,m=function(c){c?k():l();a(g).prop("checked",c).parent().parent().toggleClass(b.selectedClass,c)},h=function(){var c=a(g).filter(":checked").length;a(b.counterContainer).html(interpolate(ngettext("%(sel)s of %(cnt)s selected","%(sel)s of %(cnt)s selected",c),{sel:c,cnt:_actions_icnt},!0));a(b.allToggle).prop("checked",function(){var a;c==g.length?(a=!0,k()):(a=!1,n());return a})},k=function(){a(b.acrossClears).hide(); 2 | a(b.acrossQuestions).show();a(b.allContainer).hide()},p=function(){a(b.acrossClears).show();a(b.acrossQuestions).hide();a(b.actionContainer).toggleClass(b.selectedClass);a(b.allContainer).show();a(b.counterContainer).hide()},l=function(){a(b.acrossClears).hide();a(b.acrossQuestions).hide();a(b.allContainer).hide();a(b.counterContainer).show()},n=function(){l();a(b.acrossInput).val(0);a(b.actionContainer).removeClass(b.selectedClass)};a(b.counterContainer).show();a(this).filter(":checked").each(function(c){a(this).parent().parent().toggleClass(b.selectedClass); 3 | h();1==a(b.acrossInput).val()&&p()});a(b.allToggle).show().click(function(){m(a(this).prop("checked"));h()});a("a",b.acrossQuestions).click(function(c){c.preventDefault();a(b.acrossInput).val(1);p()});a("a",b.acrossClears).click(function(c){c.preventDefault();a(b.allToggle).prop("checked",!1);n();m(0);h()});f=null;a(g).click(function(c){c||(c=window.event);var d=c.target?c.target:c.srcElement;if(f&&a.data(f)!=a.data(d)&&!0===c.shiftKey){var e=!1;a(f).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass, 4 | d.checked);a(g).each(function(){if(a.data(this)==a.data(f)||a.data(this)==a.data(d))e=e?!1:!0;e&&a(this).prop("checked",d.checked).parent().parent().toggleClass(b.selectedClass,d.checked)})}a(d).parent().parent().toggleClass(b.selectedClass,d.checked);f=d;h()});a("form#changelist-form table#result_list tr").find("td:gt(0) :input").change(function(){e=!0});a('form#changelist-form button[name="index"]').click(function(a){if(e)return confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."))}); 5 | a('form#changelist-form input[name="_save"]').click(function(c){var d=!1;a("select option:selected",b.actionContainer).each(function(){a(this).val()&&(d=!0)});if(d)return e?confirm(gettext("You have selected an action, but you haven't saved your changes to individual fields yet. Please click OK to save. You'll need to re-run the action.")):confirm(gettext("You have selected an action, and you haven't made any changes on individual fields. You're probably looking for the Go button rather than the Save button."))})}; 6 | a.fn.actions.defaults={actionContainer:"div.actions",counterContainer:"span.action-counter",allContainer:"div.actions span.all",acrossInput:"div.actions input.select-across",acrossQuestions:"div.actions span.question",acrossClears:"div.actions span.clear",allToggle:"#action-toggle",selectedClass:"selected"}})(django.jQuery); 7 | -------------------------------------------------------------------------------- /static/static_root/admin/js/admin/RelatedObjectLookups.js: -------------------------------------------------------------------------------- 1 | // Handles related-objects functionality: lookup link for raw_id_fields 2 | // and Add Another links. 3 | 4 | function html_unescape(text) { 5 | // Unescape a string that was escaped using django.utils.html.escape. 6 | text = text.replace(/</g, '<'); 7 | text = text.replace(/>/g, '>'); 8 | text = text.replace(/"/g, '"'); 9 | text = text.replace(/'/g, "'"); 10 | text = text.replace(/&/g, '&'); 11 | return text; 12 | } 13 | 14 | // IE doesn't accept periods or dashes in the window name, but the element IDs 15 | // we use to generate popup window names may contain them, therefore we map them 16 | // to allowed characters in a reversible way so that we can locate the correct 17 | // element when the popup window is dismissed. 18 | function id_to_windowname(text) { 19 | text = text.replace(/\./g, '__dot__'); 20 | text = text.replace(/\-/g, '__dash__'); 21 | return text; 22 | } 23 | 24 | function windowname_to_id(text) { 25 | text = text.replace(/__dot__/g, '.'); 26 | text = text.replace(/__dash__/g, '-'); 27 | return text; 28 | } 29 | 30 | function showRelatedObjectLookupPopup(triggeringLink) { 31 | var name = triggeringLink.id.replace(/^lookup_/, ''); 32 | name = id_to_windowname(name); 33 | var href; 34 | if (triggeringLink.href.search(/\?/) >= 0) { 35 | href = triggeringLink.href + '&_popup=1'; 36 | } else { 37 | href = triggeringLink.href + '?_popup=1'; 38 | } 39 | var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes'); 40 | win.focus(); 41 | return false; 42 | } 43 | 44 | function dismissRelatedLookupPopup(win, chosenId) { 45 | var name = windowname_to_id(win.name); 46 | var elem = document.getElementById(name); 47 | if (elem.className.indexOf('vManyToManyRawIdAdminField') != -1 && elem.value) { 48 | elem.value += ',' + chosenId; 49 | } else { 50 | document.getElementById(name).value = chosenId; 51 | } 52 | win.close(); 53 | } 54 | 55 | function showAddAnotherPopup(triggeringLink) { 56 | var name = triggeringLink.id.replace(/^add_/, ''); 57 | name = id_to_windowname(name); 58 | var href = triggeringLink.href; 59 | if (href.indexOf('?') == -1) { 60 | href += '?_popup=1'; 61 | } else { 62 | href += '&_popup=1'; 63 | } 64 | var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes'); 65 | win.focus(); 66 | return false; 67 | } 68 | 69 | function dismissAddAnotherPopup(win, newId, newRepr) { 70 | // newId and newRepr are expected to have previously been escaped by 71 | // django.utils.html.escape. 72 | newId = html_unescape(newId); 73 | newRepr = html_unescape(newRepr); 74 | var name = windowname_to_id(win.name); 75 | var elem = document.getElementById(name); 76 | var o; 77 | if (elem) { 78 | var elemName = elem.nodeName.toUpperCase(); 79 | if (elemName == 'SELECT') { 80 | o = new Option(newRepr, newId); 81 | elem.options[elem.options.length] = o; 82 | o.selected = true; 83 | } else if (elemName == 'INPUT') { 84 | if (elem.className.indexOf('vManyToManyRawIdAdminField') != -1 && elem.value) { 85 | elem.value += ',' + newId; 86 | } else { 87 | elem.value = newId; 88 | } 89 | } 90 | } else { 91 | var toId = name + "_to"; 92 | o = new Option(newRepr, newId); 93 | SelectBox.add_to_cache(toId, o); 94 | SelectBox.redisplay(toId); 95 | } 96 | win.close(); 97 | } 98 | -------------------------------------------------------------------------------- /static/static_root/admin/js/calendar.js: -------------------------------------------------------------------------------- 1 | /* 2 | calendar.js - Calendar functions by Adrian Holovaty 3 | depends on core.js for utility functions like removeChildren or quickElement 4 | */ 5 | 6 | // CalendarNamespace -- Provides a collection of HTML calendar-related helper functions 7 | var CalendarNamespace = { 8 | monthsOfYear: gettext('January February March April May June July August September October November December').split(' '), 9 | daysOfWeek: gettext('S M T W T F S').split(' '), 10 | firstDayOfWeek: parseInt(get_format('FIRST_DAY_OF_WEEK')), 11 | isLeapYear: function(year) { 12 | return (((year % 4)==0) && ((year % 100)!=0) || ((year % 400)==0)); 13 | }, 14 | getDaysInMonth: function(month,year) { 15 | var days; 16 | if (month==1 || month==3 || month==5 || month==7 || month==8 || month==10 || month==12) { 17 | days = 31; 18 | } 19 | else if (month==4 || month==6 || month==9 || month==11) { 20 | days = 30; 21 | } 22 | else if (month==2 && CalendarNamespace.isLeapYear(year)) { 23 | days = 29; 24 | } 25 | else { 26 | days = 28; 27 | } 28 | return days; 29 | }, 30 | draw: function(month, year, div_id, callback, selected) { // month = 1-12, year = 1-9999 31 | var today = new Date(); 32 | var todayDay = today.getDate(); 33 | var todayMonth = today.getMonth()+1; 34 | var todayYear = today.getFullYear(); 35 | var todayClass = ''; 36 | 37 | // Use UTC functions here because the date field does not contain time 38 | // and using the UTC function variants prevent the local time offset 39 | // from altering the date, specifically the day field. For example: 40 | // 41 | // ``` 42 | // var x = new Date('2013-10-02'); 43 | // var day = x.getDate(); 44 | // ``` 45 | // 46 | // The day variable above will be 1 instead of 2 in, say, US Pacific time 47 | // zone. 48 | var isSelectedMonth = false; 49 | if (typeof selected != 'undefined') { 50 | isSelectedMonth = (selected.getUTCFullYear() == year && (selected.getUTCMonth()+1) == month); 51 | } 52 | 53 | month = parseInt(month); 54 | year = parseInt(year); 55 | var calDiv = document.getElementById(div_id); 56 | removeChildren(calDiv); 57 | var calTable = document.createElement('table'); 58 | quickElement('caption', calTable, CalendarNamespace.monthsOfYear[month-1] + ' ' + year); 59 | var tableBody = quickElement('tbody', calTable); 60 | 61 | // Draw days-of-week header 62 | var tableRow = quickElement('tr', tableBody); 63 | for (var i = 0; i < 7; i++) { 64 | quickElement('th', tableRow, CalendarNamespace.daysOfWeek[(i + CalendarNamespace.firstDayOfWeek) % 7]); 65 | } 66 | 67 | var startingPos = new Date(year, month-1, 1 - CalendarNamespace.firstDayOfWeek).getDay(); 68 | var days = CalendarNamespace.getDaysInMonth(month, year); 69 | 70 | // Draw blanks before first of month 71 | tableRow = quickElement('tr', tableBody); 72 | for (var i = 0; i < startingPos; i++) { 73 | var _cell = quickElement('td', tableRow, ' '); 74 | _cell.className = "nonday"; 75 | } 76 | 77 | // Draw days of month 78 | var currentDay = 1; 79 | for (var i = startingPos; currentDay <= days; i++) { 80 | if (i%7 == 0 && currentDay != 1) { 81 | tableRow = quickElement('tr', tableBody); 82 | } 83 | if ((currentDay==todayDay) && (month==todayMonth) && (year==todayYear)) { 84 | todayClass='today'; 85 | } else { 86 | todayClass=''; 87 | } 88 | 89 | // use UTC function; see above for explanation. 90 | if (isSelectedMonth && currentDay == selected.getUTCDate()) { 91 | if (todayClass != '') todayClass += " "; 92 | todayClass += "selected"; 93 | } 94 | 95 | var cell = quickElement('td', tableRow, '', 'class', todayClass); 96 | 97 | quickElement('a', cell, currentDay, 'href', 'javascript:void(' + callback + '('+year+','+month+','+currentDay+'));'); 98 | currentDay++; 99 | } 100 | 101 | // Draw blanks after end of month (optional, but makes for valid code) 102 | while (tableRow.childNodes.length < 7) { 103 | var _cell = quickElement('td', tableRow, ' '); 104 | _cell.className = "nonday"; 105 | } 106 | 107 | calDiv.appendChild(calTable); 108 | } 109 | } 110 | 111 | // Calendar -- A calendar instance 112 | function Calendar(div_id, callback, selected) { 113 | // div_id (string) is the ID of the element in which the calendar will 114 | // be displayed 115 | // callback (string) is the name of a JavaScript function that will be 116 | // called with the parameters (year, month, day) when a day in the 117 | // calendar is clicked 118 | this.div_id = div_id; 119 | this.callback = callback; 120 | this.today = new Date(); 121 | this.currentMonth = this.today.getMonth() + 1; 122 | this.currentYear = this.today.getFullYear(); 123 | if (typeof selected != 'undefined') { 124 | this.selected = selected; 125 | } 126 | } 127 | Calendar.prototype = { 128 | drawCurrent: function() { 129 | CalendarNamespace.draw(this.currentMonth, this.currentYear, this.div_id, this.callback, this.selected); 130 | }, 131 | drawDate: function(month, year, selected) { 132 | this.currentMonth = month; 133 | this.currentYear = year; 134 | 135 | if(selected) { 136 | this.selected = selected; 137 | } 138 | 139 | this.drawCurrent(); 140 | }, 141 | drawPreviousMonth: function() { 142 | if (this.currentMonth == 1) { 143 | this.currentMonth = 12; 144 | this.currentYear--; 145 | } 146 | else { 147 | this.currentMonth--; 148 | } 149 | this.drawCurrent(); 150 | }, 151 | drawNextMonth: function() { 152 | if (this.currentMonth == 12) { 153 | this.currentMonth = 1; 154 | this.currentYear++; 155 | } 156 | else { 157 | this.currentMonth++; 158 | } 159 | this.drawCurrent(); 160 | }, 161 | drawPreviousYear: function() { 162 | this.currentYear--; 163 | this.drawCurrent(); 164 | }, 165 | drawNextYear: function() { 166 | this.currentYear++; 167 | this.drawCurrent(); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /static/static_root/admin/js/collapse.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | $(document).ready(function() { 3 | // Add anchor tag for Show/Hide link 4 | $("fieldset.collapse").each(function(i, elem) { 5 | // Don't hide if fields in this fieldset have errors 6 | if ($(elem).find("div.errors").length == 0) { 7 | $(elem).addClass("collapsed").find("h2").first().append(' (' + gettext("Show") + 9 | ')'); 10 | } 11 | }); 12 | // Add toggle to anchor tag 13 | $("fieldset.collapse a.collapse-toggle").click(function(ev) { 14 | if ($(this).closest("fieldset").hasClass("collapsed")) { 15 | // Show 16 | $(this).text(gettext("Hide")).closest("fieldset").removeClass("collapsed").trigger("show.fieldset", [$(this).attr("id")]); 17 | } else { 18 | // Hide 19 | $(this).text(gettext("Show")).closest("fieldset").addClass("collapsed").trigger("hide.fieldset", [$(this).attr("id")]); 20 | } 21 | return false; 22 | }); 23 | }); 24 | })(django.jQuery); 25 | -------------------------------------------------------------------------------- /static/static_root/admin/js/collapse.min.js: -------------------------------------------------------------------------------- 1 | (function(a){a(document).ready(function(){a("fieldset.collapse").each(function(c,b){a(b).find("div.errors").length==0&&a(b).addClass("collapsed").find("h2").first().append(' ('+gettext("Show")+")")});a("fieldset.collapse a.collapse-toggle").click(function(){a(this).closest("fieldset").hasClass("collapsed")?a(this).text(gettext("Hide")).closest("fieldset").removeClass("collapsed").trigger("show.fieldset",[a(this).attr("id")]):a(this).text(gettext("Show")).closest("fieldset").addClass("collapsed").trigger("hide.fieldset", 2 | [a(this).attr("id")]);return false})})})(django.jQuery); 3 | -------------------------------------------------------------------------------- /static/static_root/admin/js/inlines.min.js: -------------------------------------------------------------------------------- 1 | (function(a){a.fn.formset=function(g){var b=a.extend({},a.fn.formset.defaults,g),i=a(this);g=i.parent();var m=function(e,k,h){var j=RegExp("("+k+"-(\\d+|__prefix__))");k=k+"-"+h;a(e).prop("for")&&a(e).prop("for",a(e).prop("for").replace(j,k));if(e.id)e.id=e.id.replace(j,k);if(e.name)e.name=e.name.replace(j,k)},l=a("#id_"+b.prefix+"-TOTAL_FORMS").prop("autocomplete","off"),d=parseInt(l.val(),10),c=a("#id_"+b.prefix+"-MAX_NUM_FORMS").prop("autocomplete","off");l=c.val()===""||c.val()-l.val()>0;i.each(function(){a(this).not("."+ 2 | b.emptyCssClass).addClass(b.formCssClass)});if(i.length&&l){var f;if(i.prop("tagName")=="TR"){i=this.eq(-1).children().length;g.append(''+b.addText+"");f=g.find("tr:last a")}else{i.filter(":last").after('");f=i.filter(":last").next().find("a")}f.click(function(e){e.preventDefault();var k=a("#id_"+b.prefix+"-TOTAL_FORMS");e=a("#"+ 3 | b.prefix+"-empty");var h=e.clone(true);h.removeClass(b.emptyCssClass).addClass(b.formCssClass).attr("id",b.prefix+"-"+d);if(h.is("tr"))h.children(":last").append('");else h.is("ul")||h.is("ol")?h.append('
  • '+b.deleteText+"
  • "):h.children(":first").append(''+b.deleteText+""); 4 | h.find("*").each(function(){m(this,b.prefix,k.val())});h.insertBefore(a(e));a(k).val(parseInt(k.val(),10)+1);d+=1;c.val()!==""&&c.val()-k.val()<=0&&f.parent().hide();h.find("a."+b.deleteCssClass).click(function(j){j.preventDefault();j=a(this).parents("."+b.formCssClass);j.remove();d-=1;b.removed&&b.removed(j);j=a("."+b.formCssClass);a("#id_"+b.prefix+"-TOTAL_FORMS").val(j.length);if(c.val()===""||c.val()-j.length>0)f.parent().show();for(var n=0,o=j.length;n 0) { 23 | values.push(field.val()); 24 | } 25 | }); 26 | prepopulatedField.val(URLify(values.join(' '), maxLength)); 27 | }; 28 | 29 | prepopulatedField.data('_changed', false); 30 | prepopulatedField.change(function() { 31 | prepopulatedField.data('_changed', true); 32 | }); 33 | 34 | if (!prepopulatedField.val()) { 35 | $(dependencies.join(',')).keyup(populate).change(populate).focus(populate); 36 | } 37 | }); 38 | }; 39 | })(django.jQuery); 40 | -------------------------------------------------------------------------------- /static/static_root/admin/js/prepopulate.min.js: -------------------------------------------------------------------------------- 1 | (function(b){b.fn.prepopulate=function(e,g){return this.each(function(){var a=b(this),d=function(){if(!a.data("_changed")){var f=[];b.each(e,function(h,c){c=b(c);c.val().length>0&&f.push(c.val())});a.val(URLify(f.join(" "),g))}};a.data("_changed",false);a.change(function(){a.data("_changed",true)});a.val()||b(e.join(",")).keyup(d).change(d).focus(d)})}})(django.jQuery); 2 | -------------------------------------------------------------------------------- /static/static_root/admin/js/timeparse.js: -------------------------------------------------------------------------------- 1 | var timeParsePatterns = [ 2 | // 9 3 | { re: /^\d{1,2}$/i, 4 | handler: function(bits) { 5 | if (bits[0].length == 1) { 6 | return '0' + bits[0] + ':00'; 7 | } else { 8 | return bits[0] + ':00'; 9 | } 10 | } 11 | }, 12 | // 13:00 13 | { re: /^\d{2}[:.]\d{2}$/i, 14 | handler: function(bits) { 15 | return bits[0].replace('.', ':'); 16 | } 17 | }, 18 | // 9:00 19 | { re: /^\d[:.]\d{2}$/i, 20 | handler: function(bits) { 21 | return '0' + bits[0].replace('.', ':'); 22 | } 23 | }, 24 | // 3 am / 3 a.m. / 3am 25 | { re: /^(\d+)\s*([ap])(?:.?m.?)?$/i, 26 | handler: function(bits) { 27 | var hour = parseInt(bits[1]); 28 | if (hour == 12) { 29 | hour = 0; 30 | } 31 | if (bits[2].toLowerCase() == 'p') { 32 | if (hour == 12) { 33 | hour = 0; 34 | } 35 | return (hour + 12) + ':00'; 36 | } else { 37 | if (hour < 10) { 38 | return '0' + hour + ':00'; 39 | } else { 40 | return hour + ':00'; 41 | } 42 | } 43 | } 44 | }, 45 | // 3.30 am / 3:15 a.m. / 3.00am 46 | { re: /^(\d+)[.:](\d{2})\s*([ap]).?m.?$/i, 47 | handler: function(bits) { 48 | var hour = parseInt(bits[1]); 49 | var mins = parseInt(bits[2]); 50 | if (mins < 10) { 51 | mins = '0' + mins; 52 | } 53 | if (hour == 12) { 54 | hour = 0; 55 | } 56 | if (bits[3].toLowerCase() == 'p') { 57 | if (hour == 12) { 58 | hour = 0; 59 | } 60 | return (hour + 12) + ':' + mins; 61 | } else { 62 | if (hour < 10) { 63 | return '0' + hour + ':' + mins; 64 | } else { 65 | return hour + ':' + mins; 66 | } 67 | } 68 | } 69 | }, 70 | // noon 71 | { re: /^no/i, 72 | handler: function(bits) { 73 | return '12:00'; 74 | } 75 | }, 76 | // midnight 77 | { re: /^mid/i, 78 | handler: function(bits) { 79 | return '00:00'; 80 | } 81 | } 82 | ]; 83 | 84 | function parseTimeString(s) { 85 | for (var i = 0; i < timeParsePatterns.length; i++) { 86 | var re = timeParsePatterns[i].re; 87 | var handler = timeParsePatterns[i].handler; 88 | var bits = re.exec(s); 89 | if (bits) { 90 | return handler(bits); 91 | } 92 | } 93 | return s; 94 | } 95 | -------------------------------------------------------------------------------- /static/static_root/admin/js/urlify.js: -------------------------------------------------------------------------------- 1 | var LATIN_MAP = { 2 | 'À': 'A', 'Á': 'A', 'Â': 'A', 'Ã': 'A', 'Ä': 'A', 'Å': 'A', 'Æ': 'AE', 'Ç': 3 | 'C', 'È': 'E', 'É': 'E', 'Ê': 'E', 'Ë': 'E', 'Ì': 'I', 'Í': 'I', 'Î': 'I', 4 | 'Ï': 'I', 'Ð': 'D', 'Ñ': 'N', 'Ò': 'O', 'Ó': 'O', 'Ô': 'O', 'Õ': 'O', 'Ö': 5 | 'O', 'Ő': 'O', 'Ø': 'O', 'Ù': 'U', 'Ú': 'U', 'Û': 'U', 'Ü': 'U', 'Ű': 'U', 6 | 'Ý': 'Y', 'Þ': 'TH', 'Ÿ': 'Y', 'ß': 'ss', 'à':'a', 'á':'a', 'â': 'a', 'ã': 7 | 'a', 'ä': 'a', 'å': 'a', 'æ': 'ae', 'ç': 'c', 'è': 'e', 'é': 'e', 'ê': 'e', 8 | 'ë': 'e', 'ì': 'i', 'í': 'i', 'î': 'i', 'ï': 'i', 'ð': 'd', 'ñ': 'n', 'ò': 9 | 'o', 'ó': 'o', 'ô': 'o', 'õ': 'o', 'ö': 'o', 'ő': 'o', 'ø': 'o', 'ù': 'u', 10 | 'ú': 'u', 'û': 'u', 'ü': 'u', 'ű': 'u', 'ý': 'y', 'þ': 'th', 'ÿ': 'y' 11 | }; 12 | var LATIN_SYMBOLS_MAP = { 13 | '©':'(c)' 14 | }; 15 | var GREEK_MAP = { 16 | 'α':'a', 'β':'b', 'γ':'g', 'δ':'d', 'ε':'e', 'ζ':'z', 'η':'h', 'θ':'8', 17 | 'ι':'i', 'κ':'k', 'λ':'l', 'μ':'m', 'ν':'n', 'ξ':'3', 'ο':'o', 'π':'p', 18 | 'ρ':'r', 'σ':'s', 'τ':'t', 'υ':'y', 'φ':'f', 'χ':'x', 'ψ':'ps', 'ω':'w', 19 | 'ά':'a', 'έ':'e', 'ί':'i', 'ό':'o', 'ύ':'y', 'ή':'h', 'ώ':'w', 'ς':'s', 20 | 'ϊ':'i', 'ΰ':'y', 'ϋ':'y', 'ΐ':'i', 21 | 'Α':'A', 'Β':'B', 'Γ':'G', 'Δ':'D', 'Ε':'E', 'Ζ':'Z', 'Η':'H', 'Θ':'8', 22 | 'Ι':'I', 'Κ':'K', 'Λ':'L', 'Μ':'M', 'Ν':'N', 'Ξ':'3', 'Ο':'O', 'Π':'P', 23 | 'Ρ':'R', 'Σ':'S', 'Τ':'T', 'Υ':'Y', 'Φ':'F', 'Χ':'X', 'Ψ':'PS', 'Ω':'W', 24 | 'Ά':'A', 'Έ':'E', 'Ί':'I', 'Ό':'O', 'Ύ':'Y', 'Ή':'H', 'Ώ':'W', 'Ϊ':'I', 25 | 'Ϋ':'Y' 26 | }; 27 | var TURKISH_MAP = { 28 | 'ş':'s', 'Ş':'S', 'ı':'i', 'İ':'I', 'ç':'c', 'Ç':'C', 'ü':'u', 'Ü':'U', 29 | 'ö':'o', 'Ö':'O', 'ğ':'g', 'Ğ':'G' 30 | }; 31 | var RUSSIAN_MAP = { 32 | 'а':'a', 'б':'b', 'в':'v', 'г':'g', 'д':'d', 'е':'e', 'ё':'yo', 'ж':'zh', 33 | 'з':'z', 'и':'i', 'й':'j', 'к':'k', 'л':'l', 'м':'m', 'н':'n', 'о':'o', 34 | 'п':'p', 'р':'r', 'с':'s', 'т':'t', 'у':'u', 'ф':'f', 'х':'h', 'ц':'c', 35 | 'ч':'ch', 'ш':'sh', 'щ':'sh', 'ъ':'', 'ы':'y', 'ь':'', 'э':'e', 'ю':'yu', 36 | 'я':'ya', 37 | 'А':'A', 'Б':'B', 'В':'V', 'Г':'G', 'Д':'D', 'Е':'E', 'Ё':'Yo', 'Ж':'Zh', 38 | 'З':'Z', 'И':'I', 'Й':'J', 'К':'K', 'Л':'L', 'М':'M', 'Н':'N', 'О':'O', 39 | 'П':'P', 'Р':'R', 'С':'S', 'Т':'T', 'У':'U', 'Ф':'F', 'Х':'H', 'Ц':'C', 40 | 'Ч':'Ch', 'Ш':'Sh', 'Щ':'Sh', 'Ъ':'', 'Ы':'Y', 'Ь':'', 'Э':'E', 'Ю':'Yu', 41 | 'Я':'Ya' 42 | }; 43 | var UKRAINIAN_MAP = { 44 | 'Є':'Ye', 'І':'I', 'Ї':'Yi', 'Ґ':'G', 'є':'ye', 'і':'i', 'ї':'yi', 'ґ':'g' 45 | }; 46 | var CZECH_MAP = { 47 | 'č':'c', 'ď':'d', 'ě':'e', 'ň': 'n', 'ř':'r', 'š':'s', 'ť':'t', 'ů':'u', 48 | 'ž':'z', 'Č':'C', 'Ď':'D', 'Ě':'E', 'Ň': 'N', 'Ř':'R', 'Š':'S', 'Ť':'T', 49 | 'Ů':'U', 'Ž':'Z' 50 | }; 51 | var POLISH_MAP = { 52 | 'ą':'a', 'ć':'c', 'ę':'e', 'ł':'l', 'ń':'n', 'ó':'o', 'ś':'s', 'ź':'z', 53 | 'ż':'z', 'Ą':'A', 'Ć':'C', 'Ę':'E', 'Ł':'L', 'Ń':'N', 'Ó':'O', 'Ś':'S', 54 | 'Ź':'Z', 'Ż':'Z' 55 | }; 56 | var LATVIAN_MAP = { 57 | 'ā':'a', 'č':'c', 'ē':'e', 'ģ':'g', 'ī':'i', 'ķ':'k', 'ļ':'l', 'ņ':'n', 58 | 'š':'s', 'ū':'u', 'ž':'z', 'Ā':'A', 'Č':'C', 'Ē':'E', 'Ģ':'G', 'Ī':'I', 59 | 'Ķ':'K', 'Ļ':'L', 'Ņ':'N', 'Š':'S', 'Ū':'U', 'Ž':'Z' 60 | }; 61 | var ARABIC_MAP = { 62 | 'أ':'a', 'ب':'b', 'ت':'t', 'ث': 'th', 'ج':'g', 'ح':'h', 'خ':'kh', 'د':'d', 63 | 'ذ':'th', 'ر':'r', 'ز':'z', 'س':'s', 'ش':'sh', 'ص':'s', 'ض':'d', 'ط':'t', 64 | 'ظ':'th', 'ع':'aa', 'غ':'gh', 'ف':'f', 'ق':'k', 'ك':'k', 'ل':'l', 'م':'m', 65 | 'ن':'n', 'ه':'h', 'و':'o', 'ي':'y' 66 | }; 67 | var LITHUANIAN_MAP = { 68 | 'ą':'a', 'č':'c', 'ę':'e', 'ė':'e', 'į':'i', 'š':'s', 'ų':'u', 'ū':'u', 69 | 'ž':'z', 70 | 'Ą':'A', 'Č':'C', 'Ę':'E', 'Ė':'E', 'Į':'I', 'Š':'S', 'Ų':'U', 'Ū':'U', 71 | 'Ž':'Z' 72 | }; 73 | var SERBIAN_MAP = { 74 | 'ђ':'dj', 'ј':'j', 'љ':'lj', 'њ':'nj', 'ћ':'c', 'џ':'dz', 'đ':'dj', 75 | 'Ђ':'Dj', 'Ј':'j', 'Љ':'Lj', 'Њ':'Nj', 'Ћ':'C', 'Џ':'Dz', 'Đ':'Dj' 76 | }; 77 | var AZERBAIJANI_MAP = { 78 | 'ç':'c', 'ə':'e', 'ğ':'g', 'ı':'i', 'ö':'o', 'ş':'s', 'ü':'u', 79 | 'Ç':'C', 'Ə':'E', 'Ğ':'G', 'İ':'I', 'Ö':'O', 'Ş':'S', 'Ü':'U' 80 | }; 81 | 82 | var ALL_DOWNCODE_MAPS = [ 83 | LATIN_MAP, 84 | LATIN_SYMBOLS_MAP, 85 | GREEK_MAP, 86 | TURKISH_MAP, 87 | RUSSIAN_MAP, 88 | UKRAINIAN_MAP, 89 | CZECH_MAP, 90 | POLISH_MAP, 91 | LATVIAN_MAP, 92 | ARABIC_MAP, 93 | LITHUANIAN_MAP, 94 | SERBIAN_MAP, 95 | AZERBAIJANI_MAP 96 | ]; 97 | 98 | var Downcoder = { 99 | 'Initialize': function() { 100 | if (Downcoder.map) { // already made 101 | return; 102 | } 103 | Downcoder.map = {}; 104 | Downcoder.chars = []; 105 | for (var i=0; i -1 || ua.indexOf('Presto') > -1) { 40 | return // Opera, which might pretend to be IE 41 | } 42 | var emulated = emulatedIEMajorVersion() 43 | if (emulated === null) { 44 | return // Not IE 45 | } 46 | var nonEmulated = actualNonEmulatedIEMajorVersion() 47 | 48 | if (emulated !== nonEmulated) { 49 | window.alert('WARNING: You appear to be using IE' + nonEmulated + ' in IE' + emulated + ' emulation mode.\nIE emulation modes can behave significantly differently from ACTUAL older versions of IE.\nPLEASE DON\'T FILE BOOTSTRAP BUGS based on testing in IE emulation modes!') 50 | } 51 | })(); 52 | -------------------------------------------------------------------------------- /static/static_root/js/ie10-viewport-bug-workaround.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * IE10 viewport hack for Surface/desktop Windows 8 bug 3 | * Copyright 2014 Twitter, Inc. 4 | * Licensed under the Creative Commons Attribution 3.0 Unported License. For 5 | * details, see http://creativecommons.org/licenses/by/3.0/. 6 | */ 7 | 8 | // See the Getting Started docs for more information: 9 | // http://getbootstrap.com/getting-started/#support-ie10-width 10 | 11 | (function () { 12 | 'use strict'; 13 | if (navigator.userAgent.match(/IEMobile\/10\.0/)) { 14 | var msViewportStyle = document.createElement('style') 15 | msViewportStyle.appendChild( 16 | document.createTextNode( 17 | '@-ms-viewport{width:auto!important}' 18 | ) 19 | ) 20 | document.querySelector('head').appendChild(msViewportStyle) 21 | } 22 | })(); 23 | --------------------------------------------------------------------------------