{{ post.content|explosivo }}
26 | {% else %} 27 | {% cache 3600 content post.pk %} 28 |{{ post.content|explosivo }}
29 | {% endcache %} 30 | {% endif %} 31 |├── __init__.py ├── deploy ├── __init__.py └── wsgi.py ├── apps └── codrspace │ ├── __init__.py │ ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── create_api_keys.py │ ├── pygments │ ├── __init__.py │ └── styles │ │ ├── __init__.py │ │ ├── solarized.py │ │ └── github.py │ ├── templatetags │ ├── __init__.py │ ├── syntax_color.py │ ├── codrspace_tags.py │ └── short_codes.py │ ├── static │ ├── favicon.ico │ ├── list-check.png │ ├── underline.png │ ├── wmd-buttons.png │ ├── codrspace-nav-logo.png │ ├── codrspace-logo-small.png │ ├── glyphicons-halflings.png │ ├── glyphicons-halflings-white.png │ ├── codrspace.js │ ├── jquery.insertat.js │ ├── bootstrap-alert.js │ ├── bootstrap-dropdown.js │ ├── bootstrap-popover.js │ ├── jquery.labelify.js │ ├── codrspace_admin.js │ ├── pygments.css │ ├── wmd.css │ ├── jquery.hotkeys.js │ ├── anytimec.css │ ├── codrspace.css │ ├── bootstrap-modal.js │ ├── bootstrap-tooltip.js │ └── bootstrap.min.js │ ├── templates │ ├── home_shutdown.html │ ├── _messages.html │ ├── feeds │ │ └── post_description.html │ ├── preview.html │ ├── _partial-media-list.html │ ├── _form_messages.html │ ├── recent_codrs.html │ ├── auth_base.html │ ├── lastest_posts.html │ ├── 500.html │ ├── auth_error.html │ ├── _user_detail.html │ ├── top_posters.html │ ├── donate.html │ ├── feedback.html │ ├── post_download.html │ ├── 404.html │ ├── _nav_auth.html │ ├── _partial-post-list.html │ ├── _post_content.html │ ├── post_list_shutdown.html │ ├── help.html │ ├── delete.html │ ├── _shortcode_help.html │ ├── _nav_shutdown.html │ ├── settings.html │ ├── home.html │ ├── base.html │ ├── _nav.html │ ├── post_detail.html │ ├── add.html │ ├── edit.html │ ├── post_list.html │ └── api_settings.html │ ├── tests.py │ ├── site_maps.py │ ├── managers.py │ ├── context_processors.py │ ├── backend.py │ ├── feeds.py │ ├── utils.py │ ├── mock_views.py │ ├── urls.py │ ├── api.py │ ├── models.py │ ├── forms.py │ └── views.py ├── .gitignore ├── requirements.pip ├── requirements_dev.pip ├── Dockerfile ├── manage.py ├── docker-compose.yml ├── urls.py ├── README.md ├── LICENSE ├── example_local_settings.py ├── INSTALL.md └── settings.py /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /deploy/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/codrspace/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/codrspace/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/codrspace/pygments/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/codrspace/pygments/styles/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/codrspace/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/codrspace/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/codrspace/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codrspace/codrspace/HEAD/apps/codrspace/static/favicon.ico -------------------------------------------------------------------------------- /apps/codrspace/static/list-check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codrspace/codrspace/HEAD/apps/codrspace/static/list-check.png -------------------------------------------------------------------------------- /apps/codrspace/static/underline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codrspace/codrspace/HEAD/apps/codrspace/static/underline.png -------------------------------------------------------------------------------- /apps/codrspace/static/wmd-buttons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codrspace/codrspace/HEAD/apps/codrspace/static/wmd-buttons.png -------------------------------------------------------------------------------- /apps/codrspace/static/codrspace-nav-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codrspace/codrspace/HEAD/apps/codrspace/static/codrspace-nav-logo.png -------------------------------------------------------------------------------- /apps/codrspace/static/codrspace-logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codrspace/codrspace/HEAD/apps/codrspace/static/codrspace-logo-small.png -------------------------------------------------------------------------------- /apps/codrspace/static/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codrspace/codrspace/HEAD/apps/codrspace/static/glyphicons-halflings.png -------------------------------------------------------------------------------- /apps/codrspace/static/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codrspace/codrspace/HEAD/apps/codrspace/static/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /apps/codrspace/static/codrspace.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function(){ 2 | // click to dismiss messages 3 | $('ul.messages').click(function() { 4 | $(this).fadeOut(800); 5 | }); 6 | }); 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | *.pyc 4 | *.swp 5 | *.db 6 | *.sqlite 7 | *.swo 8 | *.script 9 | local_settings.py 10 | epio_settings.py 11 | linode_settings.py 12 | site_media 13 | cache 14 | mockups 15 | -------------------------------------------------------------------------------- /apps/codrspace/templates/home_shutdown.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% load codrspace_tags %} 4 | {% load cache %} 5 | 6 | {% block title %}{{ SITE_TAGLINE }}{% endblock %} 7 | 8 | {% block extra_head %}{% endblock %} 9 | 10 | {% block content %}{% endblock %} 11 | -------------------------------------------------------------------------------- /requirements.pip: -------------------------------------------------------------------------------- 1 | Django==1.5.1 2 | beautifulsoup4==4.2.1 3 | django-redis-cache 4 | Markdown==2.0.3 5 | django-timezones==0.2 6 | pytz>=2011n 7 | Pygments 8 | python-mimeparse==1.6.0 9 | django-tastypie==0.12.0 10 | -e git://github.com/kennethreitz/requests.git#egg=requests 11 | -------------------------------------------------------------------------------- /apps/codrspace/templates/_messages.html: -------------------------------------------------------------------------------- 1 | {% if messages %} 2 | {% for message in messages %} 3 |
7 | {% endfor %} 8 | {% endif %} 9 | -------------------------------------------------------------------------------- /apps/codrspace/templates/feeds/post_description.html: -------------------------------------------------------------------------------- 1 | {% load short_codes %} 2 | {% load cache %} 3 | 4 | 5 | {% if cache == 0 %} 6 | {{ obj.content|explosivo }} 7 | {% else %} 8 | {% cache 3600 content_feed obj.pk %} 9 | {{ obj.content|explosivo }} 10 | {% endcache %} 11 | {% endif %} 12 | -------------------------------------------------------------------------------- /apps/codrspace/templates/preview.html: -------------------------------------------------------------------------------- 1 | {% load codrspace_tags %} 2 | {% load short_codes %} 3 | 4 | {% block content %} 5 |{{ post.content|explosivo }}
8 |We have been notified and are working hard to fix it!
18 |Thank you for your patience.
19 |9 | Looks like we couldn't get your Github 10 | information. This is pretty critical to your codrspace account. 11 |
12 | 13 |14 | Github reported the following problem: 15 |
16 |{{ err|safe }}
17 |{% firstof user_settings.name meta.name meta.login %}
4 | {% if meta.location %} 5 |{{ meta.location }}
6 | {% endif %} 7 |{% filter striptags|truncatewords:15 %}{% firstof user_settings.bio meta.bio %}{% endfilter %}
8 |Public Repos: {{meta.public_repos}}
9 |Followers: {{meta.followers}}
10 |Following: {{meta.following}}
11 |What does your donation contribute to?
16 |We don't pay ourselves anything. It goes entirely to the product.
22 | 23 |Your feedback is welcome. Please let use know how we can improve, or what features you would like to see. Thanks! 14 |
24 |We couldn't find what you were looking for :(
22 |You could try:
23 |{{ post.content|explosivo }}
26 | {% else %} 27 | {% cache 3600 content post.pk %} 28 |{{ post.content|explosivo }}
29 | {% endcache %} 30 | {% endif %} 31 |The link below will download all of your posts into an archive both published and draft.
36 |It was nice running codrspace, but time and financials lead us to shut it down. Thank you for using the platform. Happy coding!
37 | Download Archive 38 | {% endblock %}} 39 | 40 | {% block footer %}{% endblock %} 41 | -------------------------------------------------------------------------------- /apps/codrspace/templates/help.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Help{% endblock %} 4 | 5 | {% block header %}{% endblock %} 6 | 7 | {% block content %} 8 |The comments on codrspace are managed by Disqus. You must register an account on Disqus and provide codrspace with a shortname. Then you must put the shortname in your settings
16 |17 |
What is a shortname?
18 |Please see what is a shortname on Disqus
19 |What do I put in the Website URL field in Disqus?
21 |Please use {{ SITE_URL }}/{{ user.username }}/
How do I secure my comments to just codrspace.com?
24 |Go to http://yourshortname.disqus.com/admin/settings/advanced and under the trusted domains section put codrspace.com
{{ post.content|striptags|truncatewords:"100"}}
28 |You are about to delete this post. All good?
32 | 38 |[gist 1044977] Use Gist ID's from https://gist.github.com/(id)
5 | 6 |[code]My code goes here[/code]
8 |Or specify a language:
9 |[code lang="python"]My code goes here[/code]
10 | 11 | 12 |```python import this```
14 |
15 | ```javascript
16 | function test() {
17 | console.log('test!');
18 | }
19 | ```
[local example.txt] Upload files above and click them to insert into your post
25 | 26 |28 |  Upload images above and click them to insert into your post 29 |
30 |31 | **Bold Text** 32 |
33 |34 | *Italic Text* 35 |
36 |For more help with markdown see:
37 | 38 |To start blogging you will need to sign in with your GitHub account
28 | Sign in with Github 29 | 30 | {% else %} 31 |Lets find something to do:
33 | 38 | {% endif %} 39 |A little empty ...
64 | 65 |This person hasn't posted yet :(
69 |[^```]+)+?```", re.I | re.S | re.M)
70 |
71 | if len(re.findall(pattern, value)) == 0:
72 | return (replacements, value, None,)
73 |
74 | git_styles = re.finditer(pattern, value)
75 |
76 | for gs in git_styles:
77 | try:
78 | lang = gs.group('lang')
79 | except IndexError:
80 | lang = None
81 |
82 | text = _colorize_table(gs.group('code'), lang=lang)
83 | text_hash = md5(text.encode('utf-8')).hexdigest()
84 |
85 | replacements.append([text_hash, text])
86 | value = re.sub(pattern, text_hash, value, count=1)
87 |
88 | return (replacements, value, True,)
89 |
90 |
91 | def filter_inline(value):
92 | replacements = []
93 | pattern = re.compile('\\[code(\\s+lang=\"(?P[\\w]+)\")*\\](?P.*?)\\[/code\\]', re.I | re.S | re.M)
94 |
95 | if len(re.findall(pattern, value)) == 0:
96 | return (replacements, value, None,)
97 |
98 | inlines = re.finditer(pattern, value)
99 |
100 | for inline_code in inlines:
101 | try:
102 | lang = inline_code.group('lang')
103 | except IndexError:
104 | lang = None
105 |
106 | text = _colorize_table(inline_code.group('code'), lang=lang)
107 | text_hash = md5(text.encode('utf-8')).hexdigest()
108 |
109 | replacements.append([text_hash, text])
110 | value = re.sub(pattern, text_hash, value, count=1)
111 |
112 | return (replacements, value, True,)
113 |
114 |
115 | def filter_gist(value):
116 | gist_base_url = 'https://api.github.com/gists/'
117 | replacements = []
118 | pattern = re.compile('\[gist ([a-f0-9]+) *\]', flags=re.IGNORECASE)
119 |
120 | ids = re.findall(pattern, value)
121 | if not len(ids):
122 | return (replacements, value, None,)
123 |
124 | for gist_id in ids:
125 | gist_text = ""
126 | lang = None
127 | resp = requests.get('%s%s' % (gist_base_url, gist_id))
128 |
129 | if resp.status_code != 200:
130 | return (replacements, value, None,)
131 |
132 | content = simplejson.loads(resp.content)
133 |
134 | # Go through all files in gist and smash 'em together
135 | for name in content['files']:
136 | _file = content['files'][name]
137 |
138 | # try and get the language of the file either
139 | # by passing filename or by passing the language
140 | # specified
141 | if 'filename' in _file:
142 | lang = _file['filename']
143 | elif 'language' in _file:
144 | lang= _file['language']
145 |
146 | gist_text += "%s" % (
147 | _colorize_table(_file['content'], lang=lang))
148 |
149 | if content['comments'] > 0:
150 | gist_text += '
Join the conversation on ' + \
151 | 'github (%d comments)
' % (
152 | content['html_url'], content['comments'])
153 |
154 | text_hash = md5(gist_text.encode('utf-8')).hexdigest()
155 |
156 | replacements.append([text_hash, gist_text])
157 | value = re.sub(pattern, text_hash, value, count=1)
158 |
159 | return (replacements, value, True,)
160 |
161 |
162 | def filter_upload(value):
163 | replacements = []
164 | pattern = re.compile('\[local (\S+) *\]', flags=re.IGNORECASE)
165 |
166 | files = re.findall(pattern, value)
167 | if not len(files):
168 | return (replacements, value, None,)
169 |
170 | for file_name in files:
171 | colorize = True
172 | file_path = os.path.join(MEDIA_ROOT, file_name)
173 | (file_type, encoding) = mimetypes.guess_type(file_path)
174 |
175 | if file_type is None:
176 | colorize = False
177 |
178 | # FIXME: Can we trust the 'guessed' mimetype?
179 | if file_type in ['application', 'text']:
180 | colorize = False
181 |
182 | # FIXME: Limit to 1MB right now
183 | try:
184 | f = open(file_path)
185 | text = f.read(1048576)
186 | f.close()
187 | except IOError:
188 | colorize = False
189 |
190 | if colorize:
191 | text = _colorize_table(text, lang=file_name)
192 | text_hash = md5(text.encode('utf-8')).hexdigest()
193 | else:
194 | text = '[local %s]' % file_name
195 | text_hash = md5(text.encode('utf-8')).hexdigest()
196 |
197 | replacements.append([text_hash, text])
198 | value = re.sub(pattern, text_hash, value, count=1)
199 |
200 | return (replacements, value, True,)
201 |
--------------------------------------------------------------------------------
/apps/codrspace/forms.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from collections import OrderedDict
3 |
4 | from django import forms
5 | from django.conf import settings
6 | from django.utils.text import slugify
7 |
8 | from codrspace.models import Post, Media, Setting, STATUS_CHOICES
9 | from codrspace.utils import localize_date
10 |
11 | VALID_STATUS_CHOICES = ', '.join([sc[0] for sc in STATUS_CHOICES])
12 |
13 |
14 | class PostForm(forms.ModelForm):
15 | content = forms.CharField(
16 | widget=forms.Textarea(attrs={'class': 'wmd-input'}),
17 | required=False
18 | )
19 |
20 | publish_dt = forms.DateTimeField(
21 | input_formats=['%a, %b %d %Y %I:%M %p'],
22 | required=False
23 | )
24 |
25 | class Meta:
26 | model = Post
27 |
28 | def clean_slug(self):
29 | slug = self.cleaned_data['slug']
30 | posts = Post.objects.filter(slug=slug, author=self.user)
31 |
32 | if len(posts) > 0:
33 | if self.instance:
34 | for post in posts:
35 | if post.pk == self.instance.pk:
36 | return slug
37 |
38 | msg = 'You already have a post with this slug "%s"' % (slug)
39 | raise forms.ValidationError(msg)
40 |
41 | return slug
42 |
43 | def clean_publish_dt(self):
44 | date = self.cleaned_data['publish_dt']
45 |
46 | if date:
47 | date = localize_date(date, from_tz=self.timezone, to_tz=settings.TIME_ZONE)
48 |
49 | return date
50 |
51 | def __init__(self, *args, **kwargs):
52 | # checking for user argument here for a more
53 | # localized form
54 | self.user = None
55 | self.timezone = None
56 |
57 | if 'user' in kwargs:
58 | self.user = kwargs.pop('user', None)
59 |
60 | super(PostForm, self).__init__(*args, **kwargs)
61 |
62 | # add span class to charfields
63 | for field in self.fields.values():
64 | if isinstance(field, forms.fields.CharField):
65 | if 'class' in field.widget.attrs:
66 | field.widget.attrs['class'] = "%s %s" % (
67 | field.widget.attrs['class'],
68 | 'span8',
69 | )
70 | else:
71 | field.widget.attrs['class'] = 'span8'
72 |
73 | # disable publish_dt if draft
74 | if not self.instance.status or self.instance.status == 'draft':
75 | self.fields['publish_dt'].widget.attrs['disabled'] = 'disabled'
76 |
77 | # get the published dt
78 | publish_dt = self.instance.publish_dt
79 | if not publish_dt:
80 | publish_dt = datetime.now()
81 |
82 | # adjust the date/time to the users preference
83 | if self.user and not self.user.is_anonymous():
84 | user_settings = Setting.objects.get(user=self.user)
85 | self.timezone = user_settings.timezone
86 |
87 | publish_dt = localize_date(publish_dt, to_tz=self.timezone)
88 | else:
89 | self.timezone = 'US/Central'
90 | publish_dt = localize_date(publish_dt, to_tz=self.timezone)
91 |
92 | self.initial['publish_dt'] = publish_dt
93 |
94 |
95 | class APIPostForm(forms.ModelForm):
96 | title = forms.CharField(max_length=200, required=True)
97 | content = forms.CharField(required=False)
98 | slug = forms.SlugField(max_length=75, required=False)
99 | status = forms.ChoiceField(choices=STATUS_CHOICES, error_messages={
100 | 'invalid_choice': 'Please use a valid status. Valid choices are %s' % VALID_STATUS_CHOICES
101 | })
102 | publish_dt = forms.DateTimeField(required=False)
103 | create_dt = forms.DateTimeField(required=False)
104 | update_dt = forms.DateTimeField(required=False)
105 |
106 | class Meta:
107 | model = Post
108 |
109 | def __init__(self, *args, **kwargs):
110 | self.user = None
111 |
112 | if 'user' in kwargs:
113 | self.user = kwargs.pop('user', None)
114 |
115 | # call the original init
116 | super(APIPostForm, self).__init__(*args, **kwargs)
117 |
118 | # order the fields so that the clean_field gets called in
119 | # a specific order which makes validation easier
120 | ordered_fields = OrderedDict([
121 | ('title', self.fields['title'],),
122 | ('content', self.fields['content'],),
123 | ('slug', self.fields['slug'],),
124 | ('publish_dt', self.fields['publish_dt'],),
125 | ('status', self.fields['status'],),
126 | ('create_dt', self.fields['create_dt'],),
127 | ('update_dt', self.fields['update_dt'],),
128 | ])
129 | self.fields = ordered_fields
130 |
131 | def clean_slug(self):
132 | slug = self.cleaned_data['slug']
133 | title = self.cleaned_data['title']
134 |
135 | # autogenerate a slug if it isn't provided
136 | if not self.instance.pk and not slug:
137 | slug = slugify(title)
138 |
139 | posts = Post.objects.filter(slug=slug, author=self.user)
140 |
141 | if len(posts) > 0:
142 | if self.instance.pk:
143 | for post in posts:
144 | if post.pk == self.instance.pk:
145 | return slug
146 |
147 | dup_post = posts[0]
148 | msg = 'You already have a post with this slug "%s" (id: %d)' % (
149 | slug, dup_post.pk)
150 | raise forms.ValidationError(msg)
151 |
152 | return slug
153 |
154 | def clean_status(self):
155 | status = self.cleaned_data['status']
156 | publish_dt = None
157 |
158 | if 'publish_dt' in self.cleaned_data:
159 | publish_dt = self.cleaned_data['publish_dt']
160 |
161 | if status == 'published' and not publish_dt:
162 | raise forms.ValidationError(
163 | 'Please set the publish date/time (publish_dt) if status is set to published. Note that publish_dt is in UTC. (GMT)'
164 | )
165 |
166 | return status
167 |
168 |
169 | class MediaForm(forms.ModelForm):
170 | class Meta:
171 | model = Media
172 |
173 |
174 | class SettingForm(forms.ModelForm):
175 | class Meta:
176 | model = Setting
177 |
178 | def __init__(self, *args, **kwargs):
179 | super(SettingForm, self).__init__(*args, **kwargs)
180 |
181 | for field in self.fields.values():
182 | if isinstance(field, forms.fields.CharField):
183 | field.widget.attrs.update({'class': 'span10'})
184 |
185 |
186 | class FeedBackForm(forms.Form):
187 | email = forms.EmailField(required=True)
188 | comments = forms.CharField(widget=forms.Textarea(), required=True)
189 |
190 | def __init__(self, *args, **kwargs):
191 | super(FeedBackForm, self).__init__(*args, **kwargs)
192 |
193 | for field in self.fields.values():
194 | if isinstance(field, forms.fields.CharField):
195 | field.widget.attrs.update({'class': 'span10'})
196 |
--------------------------------------------------------------------------------
/settings.py:
--------------------------------------------------------------------------------
1 | import os.path
2 | import sys
3 |
4 | # Paths
5 | PROJECT_ROOT_NAME = os.path.basename(os.path.dirname(__file__))
6 | PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__))
7 | APPS_PATH = os.path.join(PROJECT_ROOT, 'apps')
8 | sys.path.insert(0, APPS_PATH)
9 |
10 | DEBUG = False
11 | TEMPLATE_DEBUG = DEBUG
12 |
13 | ADMINS = (
14 | # ('Your Name', 'your_email@example.com'),
15 | )
16 |
17 | MANAGERS = ADMINS
18 |
19 | DATABASES = {
20 | 'default': {
21 | 'ENGINE': 'django.db.backends.sqlite3',
22 | 'NAME': os.path.join(PROJECT_ROOT, 'dash.db'),
23 | 'USER': '',
24 | 'PASSWORD': '',
25 | 'HOST': '',
26 | 'PORT': '',
27 | }
28 | }
29 |
30 | # Local time zone for this installation. Choices can be found here:
31 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
32 | # although not all choices may be available on all operating systems.
33 | # On Unix systems, a value of None will cause Django to use the same
34 | # timezone as the operating system.
35 | # If running in a Windows environment this must be set to the same as your
36 | # system time zone.
37 | TIME_ZONE = 'GMT'
38 |
39 | # Language code for this installation. All choices can be found here:
40 | # http://www.i18nguy.com/unicode/language-identifiers.html
41 | LANGUAGE_CODE = 'en-us'
42 |
43 | SITE_ID = 1
44 |
45 | # If you set this to False, Django will make some optimizations so as not
46 | # to load the internationalization machinery.
47 | USE_I18N = True
48 |
49 | # If you set this to False, Django will not format dates, numbers and
50 | # calendars according to the current locale
51 | USE_L10N = True
52 |
53 | # Absolute filesystem path to the directory that will hold user-uploaded files.
54 | # Example: "/home/media/media.lawrence.com/media/"
55 | MEDIA_ROOT = os.path.join(PROJECT_ROOT, 'site_media', 'media')
56 |
57 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a
58 | # trailing slash.
59 | # Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
60 | MEDIA_URL = '/site_media/media/'
61 |
62 | # Absolute path to the directory static files should be collected to.
63 | # Don't put anything in this directory yourself; store your static files
64 | # in apps' "static/" subdirectories and in STATICFILES_DIRS.
65 | # Example: "/home/media/media.lawrence.com/static/"
66 | STATIC_ROOT = os.path.join(PROJECT_ROOT, 'site_media', 'static')
67 |
68 | # URL prefix for static files.
69 | # Example: "http://media.lawrence.com/static/"
70 | STATIC_URL = '/site_media/static/'
71 |
72 | # URL prefix for admin static files -- CSS, JavaScript and images.
73 | # Make sure to use a trailing slash.
74 | # Examples: "http://foo.com/static/admin/", "/static/admin/".
75 | ADMIN_MEDIA_PREFIX = '/static/admin/'
76 |
77 | # Additional locations of static files
78 | STATICFILES_DIRS = (
79 | # Put strings here, like "/home/html/static" or "C:/www/django/static".
80 | # Always use forward slashes, even on Windows.
81 | # Don't forget to use absolute paths, not relative paths.
82 | )
83 |
84 | # List of finder classes that know how to find static files in
85 | # various locations.
86 | STATICFILES_FINDERS = (
87 | 'django.contrib.staticfiles.finders.FileSystemFinder',
88 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
89 | # 'django.contrib.staticfiles.finders.DefaultStorageFinder',
90 | )
91 |
92 | # Make this unique, and don't share it with anybody.
93 | SECRET_KEY = '^u)n(xbb7q@y3@#)7^$s1r&_-lq%r^@ipdrgbl-w4=tv-45cv1'
94 |
95 | # List of callables that know how to import templates from various sources.
96 | TEMPLATE_LOADERS = (
97 | 'django.template.loaders.filesystem.Loader',
98 | 'django.template.loaders.app_directories.Loader',
99 | # 'django.template.loaders.eggs.Loader',
100 | )
101 |
102 | TEMPLATE_CONTEXT_PROCESSORS = (
103 | 'django.contrib.auth.context_processors.auth',
104 | 'django.core.context_processors.debug',
105 | 'django.core.context_processors.i18n',
106 | 'django.core.context_processors.media',
107 | 'django.core.context_processors.static',
108 | 'django.contrib.messages.context_processors.messages',
109 | 'codrspace.context_processors.codrspace_contexts',
110 | )
111 |
112 | MIDDLEWARE_CLASSES = (
113 | 'django.middleware.common.CommonMiddleware',
114 | 'django.contrib.sessions.middleware.SessionMiddleware',
115 | 'django.middleware.csrf.CsrfViewMiddleware',
116 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
117 | 'django.contrib.messages.middleware.MessageMiddleware',
118 | 'django.middleware.cache.UpdateCacheMiddleware',
119 | 'django.middleware.common.CommonMiddleware',
120 | # 'django.middleware.cache.FetchFromCacheMiddleware',
121 | )
122 |
123 | ROOT_URLCONF = '%s.urls' % PROJECT_ROOT_NAME
124 |
125 | TEMPLATE_DIRS = (
126 | # Put strings here, like "/home/html/django_templates" or
127 | # "C:/www/django/templates".
128 | # Always use forward slashes, even on Windows.
129 | # Don't forget to use absolute paths, not relative paths.
130 | )
131 |
132 | INSTALLED_APPS = (
133 | 'django.contrib.auth',
134 | 'django.contrib.contenttypes',
135 | 'django.contrib.sessions',
136 | 'django.contrib.sites',
137 | 'django.contrib.messages',
138 | 'django.contrib.staticfiles',
139 | 'django.contrib.markup',
140 | 'django.contrib.comments',
141 | 'django.contrib.sitemaps',
142 | # Uncomment the next line to enable the admin:
143 | # 'django.contrib.admin',
144 | # Uncomment the next line to enable admin documentation:
145 | # 'django.contrib.admindocs',
146 | 'tastypie',
147 | 'codrspace',
148 | )
149 |
150 | # A sample logging configuration. The only tangible logging
151 | # performed by this configuration is to send an email to
152 | # the site admins on every HTTP 500 error.
153 | # See http://docs.djangoproject.com/en/dev/topics/logging for
154 | # more details on how to customize your logging configuration.
155 | LOGGING = {
156 | 'version': 1,
157 | 'disable_existing_loggers': False,
158 | 'handlers': {
159 | 'mail_admins': {
160 | 'level': 'ERROR',
161 | 'class': 'django.utils.log.AdminEmailHandler',
162 | }
163 | },
164 | 'loggers': {
165 | 'django.request': {
166 | 'handlers': ['mail_admins'],
167 | 'level': 'ERROR',
168 | 'propagate': True,
169 | },
170 | }
171 | }
172 |
173 | CACHES = {
174 | 'default': {
175 | 'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
176 | 'TIMEOUT': 7200
177 | }
178 | }
179 |
180 | CACHE_MIDDLEWARE_ALIAS = 'default'
181 | CACHE_MIDDLEWARE_SECONDS = 3600
182 | CACHE_MIDDLEWARE_KEY_PREFIX = 'codrspace'
183 |
184 | # Custom profile and authentication
185 | AUTH_PROFILE_MODULE = 'codrspace.Profile'
186 | AUTHENTICATION_BACKENDS = (
187 | 'codrspace.backend.ModelBackend',
188 | 'django.contrib.auth.backends.ModelBackend',
189 | )
190 |
191 | # Login Redirect
192 | LOGIN_REDIRECT_URL = '/'
193 | LOGIN_URL = '/signin/'
194 | LOGOUT_URL = '/signout/'
195 |
196 | # Information for correct authentication with github
197 | # Replace with information specific to your app, etc.
198 | GITHUB_AUTH = {
199 | 'client_id': '',
200 | 'secret': '',
201 | 'callback_url': '',
202 | 'auth_url': 'https://github.com/login/oauth/authorize',
203 | 'access_token_url': 'https://github.com/login/oauth/access_token',
204 |
205 | # Get information of authenticated user
206 | 'user_url': 'https://api.github.com/user',
207 |
208 | # Debug mode - for faking auth_url
209 | 'debug': False
210 | }
211 |
212 | # Codrspace specific
213 | SITE_URL = "http://codrspace.com"
214 | SITE_NAME = "Codrspace"
215 | SITE_TAGLINE = "The blogging platform for coders."
216 | VERSION = "0.8 alpha"
217 | ANALYTICS_CODE = ''
218 |
219 | # 500 page that has some context
220 | handler500 = 'codrspace.views.handler500'
221 |
222 | # Override any settings locally
223 | try:
224 | from local_settings import *
225 | except ImportError:
226 | pass
227 |
--------------------------------------------------------------------------------
/apps/codrspace/static/bootstrap-tooltip.js:
--------------------------------------------------------------------------------
1 | /* ===========================================================
2 | * bootstrap-tooltip.js v2.0.2
3 | * http://twitter.github.com/bootstrap/javascript.html#tooltips
4 | * Inspired by the original jQuery.tipsy by Jason Frame
5 | * ===========================================================
6 | * Copyright 2012 Twitter, Inc.
7 | *
8 | * Licensed under the Apache License, Version 2.0 (the "License");
9 | * you may not use this file except in compliance with the License.
10 | * You may obtain a copy of the License at
11 | *
12 | * http://www.apache.org/licenses/LICENSE-2.0
13 | *
14 | * Unless required by applicable law or agreed to in writing, software
15 | * distributed under the License is distributed on an "AS IS" BASIS,
16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 | * See the License for the specific language governing permissions and
18 | * limitations under the License.
19 | * ========================================================== */
20 |
21 | !function( $ ) {
22 |
23 | "use strict"
24 |
25 | /* TOOLTIP PUBLIC CLASS DEFINITION
26 | * =============================== */
27 |
28 | var Tooltip = function ( element, options ) {
29 | this.init('tooltip', element, options)
30 | }
31 |
32 | Tooltip.prototype = {
33 |
34 | constructor: Tooltip
35 |
36 | , init: function ( type, element, options ) {
37 | var eventIn
38 | , eventOut
39 |
40 | this.type = type
41 | this.$element = $(element)
42 | this.options = this.getOptions(options)
43 | this.enabled = true
44 |
45 | if (this.options.trigger != 'manual') {
46 | eventIn = this.options.trigger == 'hover' ? 'mouseenter' : 'focus'
47 | eventOut = this.options.trigger == 'hover' ? 'mouseleave' : 'blur'
48 | this.$element.on(eventIn, this.options.selector, $.proxy(this.enter, this))
49 | this.$element.on(eventOut, this.options.selector, $.proxy(this.leave, this))
50 | }
51 |
52 | this.options.selector ?
53 | (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
54 | this.fixTitle()
55 | }
56 |
57 | , getOptions: function ( options ) {
58 | options = $.extend({}, $.fn[this.type].defaults, options, this.$element.data())
59 |
60 | if (options.delay && typeof options.delay == 'number') {
61 | options.delay = {
62 | show: options.delay
63 | , hide: options.delay
64 | }
65 | }
66 |
67 | return options
68 | }
69 |
70 | , enter: function ( e ) {
71 | var self = $(e.currentTarget)[this.type](this._options).data(this.type)
72 |
73 | if (!self.options.delay || !self.options.delay.show) {
74 | self.show()
75 | } else {
76 | self.hoverState = 'in'
77 | setTimeout(function() {
78 | if (self.hoverState == 'in') {
79 | self.show()
80 | }
81 | }, self.options.delay.show)
82 | }
83 | }
84 |
85 | , leave: function ( e ) {
86 | var self = $(e.currentTarget)[this.type](this._options).data(this.type)
87 |
88 | if (!self.options.delay || !self.options.delay.hide) {
89 | self.hide()
90 | } else {
91 | self.hoverState = 'out'
92 | setTimeout(function() {
93 | if (self.hoverState == 'out') {
94 | self.hide()
95 | }
96 | }, self.options.delay.hide)
97 | }
98 | }
99 |
100 | , show: function () {
101 | var $tip
102 | , inside
103 | , pos
104 | , actualWidth
105 | , actualHeight
106 | , placement
107 | , tp
108 |
109 | if (this.hasContent() && this.enabled) {
110 | $tip = this.tip()
111 | this.setContent()
112 |
113 | if (this.options.animation) {
114 | $tip.addClass('fade')
115 | }
116 |
117 | placement = typeof this.options.placement == 'function' ?
118 | this.options.placement.call(this, $tip[0], this.$element[0]) :
119 | this.options.placement
120 |
121 | inside = /in/.test(placement)
122 |
123 | $tip
124 | .remove()
125 | .css({ top: 0, left: 0, display: 'block' })
126 | .appendTo(inside ? this.$element : document.body)
127 |
128 | pos = this.getPosition(inside)
129 |
130 | actualWidth = $tip[0].offsetWidth
131 | actualHeight = $tip[0].offsetHeight
132 |
133 | switch (inside ? placement.split(' ')[1] : placement) {
134 | case 'bottom':
135 | tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
136 | break
137 | case 'top':
138 | tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
139 | break
140 | case 'left':
141 | tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
142 | break
143 | case 'right':
144 | tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
145 | break
146 | }
147 |
148 | $tip
149 | .css(tp)
150 | .addClass(placement)
151 | .addClass('in')
152 | }
153 | }
154 |
155 | , setContent: function () {
156 | var $tip = this.tip()
157 | $tip.find('.tooltip-inner').html(this.getTitle())
158 | $tip.removeClass('fade in top bottom left right')
159 | }
160 |
161 | , hide: function () {
162 | var that = this
163 | , $tip = this.tip()
164 |
165 | $tip.removeClass('in')
166 |
167 | function removeWithAnimation() {
168 | var timeout = setTimeout(function () {
169 | $tip.off($.support.transition.end).remove()
170 | }, 500)
171 |
172 | $tip.one($.support.transition.end, function () {
173 | clearTimeout(timeout)
174 | $tip.remove()
175 | })
176 | }
177 |
178 | $.support.transition && this.$tip.hasClass('fade') ?
179 | removeWithAnimation() :
180 | $tip.remove()
181 | }
182 |
183 | , fixTitle: function () {
184 | var $e = this.$element
185 | if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
186 | $e.attr('data-original-title', $e.attr('title') || '').removeAttr('title')
187 | }
188 | }
189 |
190 | , hasContent: function () {
191 | return this.getTitle()
192 | }
193 |
194 | , getPosition: function (inside) {
195 | return $.extend({}, (inside ? {top: 0, left: 0} : this.$element.offset()), {
196 | width: this.$element[0].offsetWidth
197 | , height: this.$element[0].offsetHeight
198 | })
199 | }
200 |
201 | , getTitle: function () {
202 | var title
203 | , $e = this.$element
204 | , o = this.options
205 |
206 | title = $e.attr('data-original-title')
207 | || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
208 |
209 | title = (title || '').toString().replace(/(^\s*|\s*$)/, "")
210 |
211 | return title
212 | }
213 |
214 | , tip: function () {
215 | return this.$tip = this.$tip || $(this.options.template)
216 | }
217 |
218 | , validate: function () {
219 | if (!this.$element[0].parentNode) {
220 | this.hide()
221 | this.$element = null
222 | this.options = null
223 | }
224 | }
225 |
226 | , enable: function () {
227 | this.enabled = true
228 | }
229 |
230 | , disable: function () {
231 | this.enabled = false
232 | }
233 |
234 | , toggleEnabled: function () {
235 | this.enabled = !this.enabled
236 | }
237 |
238 | , toggle: function () {
239 | this[this.tip().hasClass('in') ? 'hide' : 'show']()
240 | }
241 |
242 | }
243 |
244 |
245 | /* TOOLTIP PLUGIN DEFINITION
246 | * ========================= */
247 |
248 | $.fn.tooltip = function ( option ) {
249 | return this.each(function () {
250 | var $this = $(this)
251 | , data = $this.data('tooltip')
252 | , options = typeof option == 'object' && option
253 | if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))
254 | if (typeof option == 'string') data[option]()
255 | })
256 | }
257 |
258 | $.fn.tooltip.Constructor = Tooltip
259 |
260 | $.fn.tooltip.defaults = {
261 | animation: true
262 | , delay: 0
263 | , selector: false
264 | , placement: 'top'
265 | , trigger: 'hover'
266 | , title: ''
267 | , template: ''
268 | }
269 |
270 | }( window.jQuery );
--------------------------------------------------------------------------------
/apps/codrspace/templates/api_settings.html:
--------------------------------------------------------------------------------
1 | {% extends "auth_base.html" %}
2 |
3 | {% block title %}API Settings{% endblock %}
4 |
5 | {% block content %}
6 | {% include "_form_messages.html" with form=form %}
7 |
8 |
9 |
10 | API Key
11 |
12 | {{ api_key.key }}
13 |
14 | Introduction
15 |
16 | Using the API is fairly simple. We currently have a single endpoint that will allow you to access your posts. If you would like to see any other endpoints please leave us some feedback and we will see if it's a fit.
17 | There are three parameters that will be required when accessing the API:
18 |
19 |
20 | format - currently only json is supported
21 | username - your username {{ user.username }}
22 | api_key - your api key {{ api_key.key }}
23 |
24 |
25 | Those must be appended to the end of the URL as a query string. Ex.
26 | {{ SITE_URL }}/api/v1/post/?format=json&username={{ user.username }}&api_key={{ api_key.key }}
27 |
28 | Posts
29 |
30 | Get all posts
31 |
32 | curl -X GET {{ SITE_URL }}/api/v1/post/?format=json&username={{ user.username }}&api_key={{ api_key.key }}
33 | Output
34 |
35 | {
36 | "meta":{
37 | "limit":20,
38 | "next":null,
39 | "offset":0,
40 | "previous":null,
41 | "total_count":2
42 | },
43 | "objects":[
44 | {
45 | "content":"This content is sooo leet.",
46 | "create_dt":"2012-03-23T15:17:09",
47 | "id":"[id]",
48 | "publish_dt":"2012-03-23T15:16:00",
49 | "resource_uri":"/api/post/[id]/",
50 | "slug":"an-leet-blog-post",
51 | "status":"published",
52 | "title":"An leet blog post",
53 | "update_dt":"2012-03-23T15:17:09",
54 | "url":"{{ SITE_URL }}/{{ user.username }}/an-leet-blog-post/"
55 | },
56 | {
57 | "content":"My killer content",
58 | "create_dt":"2012-03-18T02:26:21",
59 | "id":"[id]",
60 | "publish_dt":"2012-03-18T02:25:00",
61 | "resource_uri":"/api/post/[id]/",
62 | "slug":"this-blog-post-will-be-killer",
63 | "status":"published",
64 | "title":"This blog post will be killer",
65 | "update_dt":"2012-03-23T15:16:45",
66 | "url":"{{ SITE_URL }}/{{ user.username }}/this-blog-post-will-be-killer/"
67 | }
68 | ]
69 | }
70 |
71 | Get single post
72 |
73 | curl -X GET {{ SITE_URL }}/api/v1/post/[id]/?format=json&username={{ user.username }}&api_key={{ api_key.key }}
74 | [id] must be replaced with the id of your post object
75 | Output
76 |
77 | {
78 | "content":"My killer content",
79 | "create_dt":"2012-03-18T02:26:21",
80 | "id":"[id]",
81 | "publish_dt":"2012-03-18T02:25:00",
82 | "resource_uri":"/api/post/[id]/",
83 | "slug":"this-blog-post-will-be-killer",
84 | "status":"published",
85 | "title":"This blog post will be killer",
86 | "update_dt":"2012-03-23T15:16:45",
87 | "url":"{{ SITE_URL }}/{{ user.username }}/this-blog-post-will-be-killer/"
88 | }
89 |
90 |
91 | Get post with given slug
92 |
93 | curl -X GET {{ SITE_URL }}/api/v1/post/?format=json&username={{ user.username }}&api_key={{ api_key.key }}&slug=this-blog-post-will-be-killer
94 | Output
95 |
96 | {
97 | "content":"My killer content",
98 | "create_dt":"2012-03-18T02:26:21",
99 | "id":"[id]",
100 | "publish_dt":"2012-03-18T02:25:00",
101 | "resource_uri":"/api/post/[id]/",
102 | "slug":"this-blog-post-will-be-killer",
103 | "status":"published",
104 | "title":"This blog post will be killer",
105 | "update_dt":"2012-03-23T15:16:45",
106 | "url":"{{ SITE_URL }}/{{ user.username }}/this-blog-post-will-be-killer/"
107 | }
108 |
109 | Create a post
110 |
111 | Fields
112 |
113 |
114 | - title - the title of your post
115 | - Content - the content of your post
116 | - Slug - the post slug. See Notes
117 | - status - either "published" or "draft". Defaults to "draft"
118 | - publish_dt - the date you want the post to be published. Can be in the past. See Notes
119 |
120 | Notes
121 |
122 |
123 | - Slug is not required. It will auto-generate a slug for you. If you already have a slug and the post generates one that is the same it will throw a 400 error with the slug error in the response
124 |
125 | - If you set the status to "published" you will have to set
publish_dt.publish_dt is in UTC (GMT)
126 |
127 |
128 | curl -X POST -H'Content-Type: application/json' -d'{"title": "An awsome post by me"}' {{ SITE_URL }}/api/post/?username={{ user.username }}&api_key={{ api_key.key }}
129 | Output
130 |
131 | {
132 | "content": "",
133 | "create_dt": "2013-05-27T17:52:54.436562",
134 | "id": [id],
135 | "publish_dt": null,
136 | "resource_uri": "/api/v1/post/[id]/",
137 | "slug": "an-awesome-post",
138 | "status": "draft",
139 | "title": "An awesome post",
140 | "update_dt": "2013-05-27T17:52:54.436593",
141 | "url": "{{ SITE_URL }}/glenbot/an-awesome-post/"
142 | }
143 |
144 |
145 | Update a post
146 |
147 | Fields
148 |
149 |
150 | - title - the title of your post
151 | - Content - the content of your post
152 | - Slug - the post slug. See Notes
153 | - status - either "published" or "draft". Defaults to "draft"
154 | - publish_dt - the date you want the post to be published. Can be in the past. See Notes
155 |
156 | Notes
157 |
158 |
159 | - Slug is not required. It will reuse the same slug for you unless you change it.
160 |
161 | - If you set the status to "published" you will have to set
publish_dt.publish_dt is in UTC (GMT)
162 |
163 |
164 | curl -X PUT -H'Content-Type: application/json' -d'{"title": "this post is even more awesome now"}' {{ SITE_URL }}/api/v1/post/[id]/?username={{ user.username }}&api_key={{ api_key.key }}
165 | [id] must be replaced with the id of your post object
166 | Output
167 |
168 | {
169 | "content": "",
170 | "create_dt": "2013-05-27T17:52:54",
171 | "id": [id],
172 | "pk": "[id]",
173 | "publish_dt": null,
174 | "resource_uri": "/api/v1/post/[id]/",
175 | "slug": "an-awesome-post",
176 | "status": "draft",
177 | "title": "this post is even more awesome now",
178 | "update_dt": "2013-05-27T18:06:34.239753",
179 | "url": "{{ SITE_URL }}/{{ user.username }}/an-awesome-post/"
180 | }
181 |
182 |
183 | Delete a post
184 |
185 | curl -X DELETE {{ SITE_URL }}/api/v1/post/[id]/?username={{ user.username }}&api_key={{ api_key.key }}
186 | [id] must be replaced with the id of your post object
187 | Output
188 |
189 | Nothing - you get HTTP/1.1 204 No Content
190 |
191 |
192 |
193 |
194 | {% endblock %}
195 |
--------------------------------------------------------------------------------
/apps/codrspace/views.py:
--------------------------------------------------------------------------------
1 | """Main codrspace views"""
2 | import requests
3 | from datetime import datetime
4 |
5 | from StringIO import StringIO
6 | from zipfile import ZipFile
7 |
8 | from django.http import Http404, HttpResponse
9 | from django.shortcuts import render, redirect, get_object_or_404
10 | from django.utils import simplejson
11 | from django.core.urlresolvers import reverse
12 | from django.contrib.auth.models import User
13 | from django.contrib.auth import authenticate, login, logout
14 | from django.contrib.auth.decorators import login_required
15 | from django.conf import settings
16 | from django.contrib import messages
17 | from django.db.models import Q
18 | from django.core.cache import cache
19 | from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
20 |
21 | from codrspace.models import Post, Profile, Media, Setting
22 | from codrspace.forms import PostForm, MediaForm, \
23 | SettingForm, FeedBackForm
24 |
25 |
26 | class GithubAuthError(Exception):
27 | pass
28 |
29 |
30 | def index(request, template_name="home_shutdown.html"):
31 | return render(request, template_name)
32 |
33 |
34 | def post_detail(request, username, slug, template_name="post_detail.html"):
35 | user = get_object_or_404(User, username=username)
36 |
37 | post = get_object_or_404(
38 | Post,
39 | author=user,
40 | slug=slug,)
41 |
42 | try:
43 | user_settings = Setting.objects.get(user=user)
44 | except:
45 | user_settings = None
46 |
47 | if post.status == 'draft':
48 | if post.author != request.user:
49 | raise Http404
50 |
51 | return render(request, template_name, {
52 | 'username': username,
53 | 'post': post,
54 | 'meta': user.profile.get_meta(),
55 | 'user_settings': user_settings
56 | })
57 |
58 |
59 | def post_list(request, username, post_type='published',
60 | template_name="post_list_shutdown.html"):
61 | user = get_object_or_404(User, username=username)
62 |
63 | try:
64 | user_settings = Setting.objects.get(user=user)
65 | except:
66 | user_settings = None
67 |
68 | if post_type == 'published':
69 | post_type = 'posts'
70 | status_query = Q(status="published")
71 | else:
72 | post_type = 'drafts'
73 | status_query = Q(status="draft")
74 |
75 | posts = Post.objects.filter(
76 | status_query,
77 | Q(publish_dt__lte=datetime.now()) | Q(publish_dt=None),
78 | author=user,
79 | )
80 | posts = posts.order_by('-publish_dt')
81 |
82 | # paginate posts
83 | paginator = Paginator(posts, 3)
84 | page = request.GET.get('page')
85 |
86 | try:
87 | posts = paginator.page(page)
88 | except PageNotAnInteger:
89 | # If page is not an integer, deliver first page.
90 | posts = paginator.page(1)
91 | except EmptyPage:
92 | # If page is out of range (e.g. 9999), deliver last page of results.
93 | posts = paginator.page(paginator.num_pages)
94 |
95 | return render(request, template_name, {
96 | 'username': username,
97 | 'posts': posts,
98 | 'post_type': post_type,
99 | 'meta': user.profile.get_meta(),
100 | 'user_settings': user_settings
101 | })
102 |
103 | @login_required
104 | def drafts(request):
105 | return post_list(request, request.user.username, post_type='drafts')
106 |
107 |
108 | @login_required
109 | def add(request, template_name="add.html"):
110 | """ Add a post """
111 |
112 | posts = Post.objects.filter(
113 | author=request.user,
114 | status__in=['draft', 'published']
115 | ).order_by('-pk')
116 | media_set = Media.objects.filter(uploader=request.user).order_by('-pk')
117 | media_form = MediaForm()
118 |
119 | if request.method == "POST":
120 | # media
121 | media_form = MediaForm(request.POST, request.FILES)
122 | if media_form.is_valid():
123 | media = media_form.save(commit=False)
124 | media.uploader = request.user
125 | media.filename = unicode(media_form.cleaned_data.get('file', ''))
126 | media.save()
127 | messages.info(
128 | request,
129 | 'Media %s has been uploaded.' % media.filename,
130 | extra_tags='alert-success'
131 | )
132 |
133 | # post
134 | form = PostForm(request.POST, user=request.user)
135 | if form.is_valid() and 'submit_post' in request.POST:
136 | post = form.save(commit=False)
137 |
138 | # if something to submit
139 | if post.title or post.content:
140 | post.author = request.user
141 | if post.status == 'published' and not post.publish_dt:
142 | post.publish_dt = datetime.now()
143 | post.save()
144 | messages.info(
145 | request,
146 | 'Added post "%s".' % post,
147 | extra_tags='alert-success')
148 | return redirect('edit', pk=post.pk)
149 |
150 | else:
151 | form = PostForm(user=request.user)
152 |
153 | return render(request, template_name, {
154 | 'form': form,
155 | 'posts': posts,
156 | 'media_set': media_set,
157 | 'media_form': media_form,
158 | })
159 |
160 |
161 | @login_required
162 | def user_settings(request, template_name="settings.html"):
163 | """ Add/Edit a setting """
164 |
165 | user = get_object_or_404(User, username=request.user.username)
166 |
167 | try:
168 | settings = Setting.objects.get(user=user)
169 | except Setting.DoesNotExist:
170 | settings = None
171 |
172 | form = SettingForm(instance=settings)
173 |
174 | if request.method == 'POST':
175 | form = SettingForm(request.POST, instance=settings)
176 | if form.is_valid():
177 | msg = "Edited settings successfully."
178 | messages.info(request, msg, extra_tags='alert-success')
179 | settings = form.save(commit=False)
180 | settings.user = user
181 | settings.save()
182 |
183 | # clear settings cache
184 | cache_key = '%s_user_settings' % user.pk
185 | cache.set(cache_key, None)
186 |
187 | return render(request, template_name, {
188 | 'form': form,
189 | })
190 |
191 |
192 | @login_required
193 | def api_settings(request, template_name="api_settings.html"):
194 | """ View API settings """
195 |
196 | from tastypie.models import ApiKey
197 | api_key = get_object_or_404(ApiKey, user=request.user)
198 |
199 | return render(request, template_name, {
200 | 'api_key': api_key,
201 | })
202 |
203 |
204 | @login_required
205 | def delete(request, pk=0, template_name="delete.html"):
206 | """ Delete a post """
207 | post = get_object_or_404(Post, pk=pk, author=request.user)
208 | user = get_object_or_404(User, username=request.user.username)
209 |
210 | if request.method == 'POST':
211 | if 'delete-post' in request.POST:
212 | post.status = 'deleted'
213 | post.save()
214 |
215 | messages.info(request, 'Post deleted', extra_tags='alert-success')
216 |
217 | return redirect(reverse('post_list', args=[user.username]))
218 |
219 | return render(request, template_name, {
220 | 'post': post,
221 | })
222 |
223 |
224 | @login_required
225 | def edit(request, pk=0, template_name="edit.html"):
226 | """ Edit a post """
227 | post = get_object_or_404(Post, pk=pk, author=request.user)
228 | posts = Post.objects.filter(
229 | ~Q(id=post.pk),
230 | author=request.user,
231 | status__in=['draft', 'published']
232 | ).order_by('-pk')
233 | media_set = Media.objects.filter(uploader=request.user).order_by('-pk')
234 | media_form = MediaForm()
235 |
236 | if request.method == "POST":
237 |
238 | # media post
239 | if 'file' in request.FILES:
240 | media_form = MediaForm(request.POST, request.FILES)
241 | if media_form.is_valid():
242 | media = media_form.save(commit=False)
243 | media.uploader = request.user
244 | media.filename = unicode(media_form.cleaned_data.get(
245 | 'file', ''))
246 | media.save()
247 |
248 | # post post hehe
249 | if 'title' in request.POST:
250 | form = PostForm(request.POST, instance=post, user=request.user)
251 | if form.is_valid() and 'submit_post' in request.POST:
252 | post = form.save(commit=False)
253 | if post.status == 'published':
254 | if not post.publish_dt:
255 | post.publish_dt = datetime.now()
256 | if post.status == "draft":
257 | post.publish_dt = None
258 | post.save()
259 | messages.info(
260 | request,
261 | 'Edited post "%s".' % post,
262 | extra_tags='alert-success')
263 | return render(request, template_name, {
264 | 'form': form,
265 | 'post': post,
266 | 'posts': posts,
267 | 'media_set': media_set,
268 | 'media_form': media_form,
269 | })
270 |
271 | return render(request, template_name, {
272 | 'form': form,
273 | 'post': post,
274 | 'posts': posts,
275 | 'media_set': media_set,
276 | 'media_form': media_form,
277 | })
278 |
279 | form = PostForm(instance=post, user=request.user)
280 | return render(request, template_name, {
281 | 'form': form,
282 | 'post': post,
283 | 'posts': posts,
284 | 'media_set': media_set,
285 | 'media_form': media_form,
286 | })
287 |
288 |
289 | def signin_start(request, slug=None, template_name="signin.html"):
290 | """Start of OAuth signin"""
291 |
292 | return redirect('%s?client_id=%s&redirect_uri=%s' % (
293 | settings.GITHUB_AUTH['auth_url'],
294 | settings.GITHUB_AUTH['client_id'],
295 | settings.GITHUB_AUTH['callback_url']))
296 |
297 |
298 | def signout(request):
299 | if request.user.is_authenticated():
300 | logout(request)
301 | return redirect(reverse('homepage'))
302 |
303 |
304 | def _validate_github_response(resp):
305 | """Raise exception if given response has error"""
306 |
307 | if resp.status_code != 200 or 'error' in resp.content:
308 | raise GithubAuthError('code: %u content: %s' % (resp.status_code,
309 | resp.content))
310 |
311 |
312 | def _parse_github_access_token(content):
313 | """Super hackish way of parsing github access token from request"""
314 | # FIXME: Awful parsing w/ lots of assumptions
315 | # String looks like this currently
316 | # access_token=1c21852a9f19b685d6f67f4409b5b4980a0c9d4f&token_type=bearer
317 | return content.split('&')[0].split('=')[1]
318 |
319 |
320 | def signin_callback(request, slug=None, template_name="base.html"):
321 | """Callback from Github OAuth"""
322 |
323 | try:
324 | code = request.GET['code']
325 | except KeyError:
326 | return render(request, 'auth_error.html', dictionary={
327 | 'err': 'Unable to get request code from Github'})
328 |
329 | resp = requests.post(url=settings.GITHUB_AUTH['access_token_url'],
330 | data={'client_id': settings.GITHUB_AUTH['client_id'],
331 | 'client_secret': settings.GITHUB_AUTH['secret'],
332 | 'code': code})
333 |
334 | try:
335 | _validate_github_response(resp)
336 | except GithubAuthError, err:
337 | return render(request, 'auth_error.html', dictionary={'err': err})
338 |
339 | token = _parse_github_access_token(resp.content)
340 |
341 | # Don't use token unless running in production b/c mocked service won't
342 | # know a valid token
343 | user_url = settings.GITHUB_AUTH['user_url']
344 |
345 | if not settings.GITHUB_AUTH['debug']:
346 | user_url = '%s?access_token=%s' % (user_url, token)
347 |
348 | resp = requests.get(user_url)
349 |
350 | try:
351 | _validate_github_response(resp)
352 | except GithubAuthError, err:
353 | return redirect(reverse('auth_error', args=[err]))
354 |
355 | github_user = simplejson.loads(resp.content)
356 |
357 | try:
358 | user = User.objects.get(username=github_user['login'])
359 | except:
360 | password = User.objects.make_random_password()
361 | user_defaults = {
362 | 'username': github_user['login'],
363 | 'is_active': True,
364 | 'is_superuser': False,
365 | 'password': password}
366 |
367 | user = User(**user_defaults)
368 |
369 | if user:
370 | user.save()
371 |
372 | # Get/Create the user profile
373 | try:
374 | profile = user.get_profile()
375 | except:
376 | profile = Profile(
377 | git_access_token=token,
378 | user=user,
379 | meta=resp.content
380 | )
381 |
382 | # update meta information and token
383 | profile.git_access_token = token
384 | profile.meta = resp.content
385 | profile.save()
386 |
387 | # Create settings for user
388 | try:
389 | user_settings = Setting.objects.get(user=user)
390 | except:
391 | user_settings = None
392 |
393 | if not user_settings:
394 | s = Setting()
395 | s.user = user
396 | s.timezone = "US/Central"
397 | s.save()
398 |
399 | # Fake auth b/c github already verified them and we aren't using our
400 | # own #passwords...yet?
401 | user.auto_login = True
402 | user = authenticate(user=user)
403 | login(request, user)
404 |
405 | return redirect(reverse('post_list', args=[user.username]))
406 |
407 |
408 | @login_required
409 | def feedback(request, template_name='feedback.html'):
410 | """ Send Feed back """
411 | from django.core.mail import EmailMessage
412 | user = get_object_or_404(User, username=request.user.username)
413 |
414 | form = FeedBackForm(initial={'email': user.email})
415 |
416 | if request.method == 'POST':
417 | form = FeedBackForm(request.POST)
418 | if form.is_valid():
419 | msg = "Thanks for send us feedback. We hope to make the product better."
420 | messages.info(request, msg, extra_tags='alert-success')
421 |
422 | subject = 'Codrspace feedback from %s' % user.username
423 | message = '%s (%s), %s' % (
424 | request.user.username,
425 | form.cleaned_data['email'],
426 | form.cleaned_data['comments'],
427 | )
428 |
429 | email = EmailMessage(
430 | subject,
431 | message,
432 | settings.DEFAULT_FROM_EMAIL,
433 | [settings.SERVER_EMAIL,],
434 | headers = {'Reply-To': form.cleaned_data['email']}
435 | )
436 | email.send(fail_silently=False)
437 |
438 | return render(request, template_name, {
439 | 'form': form,
440 | })
441 |
442 | @login_required
443 | def posts_download(request, username):
444 | """Download all posts as an archive"""
445 | user = get_object_or_404(User, username=username)
446 |
447 | if request.user.username != username:
448 | raise Http404
449 |
450 | try:
451 | user_settings = Setting.objects.get(user=user)
452 | except:
453 | user_settings = None
454 |
455 | posts = Post.objects.filter(author=user)
456 | io_buffer = StringIO()
457 | zip = ZipFile(io_buffer, "a")
458 |
459 | for post in posts:
460 | zip.writestr("{}.md".format(post.slug), post.content.encode('utf-8'))
461 |
462 | # fix for Linux zip files read in Windows
463 | for file in zip.filelist:
464 | file.create_system = 0
465 |
466 | zip.close()
467 |
468 | response = HttpResponse(mimetype="application/zip")
469 | response["Content-Disposition"] = "attachment; filename=codrspace_post_archive_{}.zip".format(username)
470 |
471 | io_buffer.seek(0)
472 | response.write(io_buffer.read())
473 |
474 | return response
475 |
476 |
477 | @login_required
478 | def render_preview(request, template_name='preview.html'):
479 | """Ajax view for rendering preview of post"""
480 |
481 | # make a mock post
482 | post = {
483 | 'title': '',
484 | 'content': ''
485 | }
486 |
487 | if request.method == 'POST':
488 | if 'title' in request.POST:
489 | post['title'] = request.POST['title']
490 | if 'content' in request.POST:
491 | post['content'] = request.POST['content']
492 |
493 | return render(request, template_name, {
494 | 'post': post,
495 | })
496 |
497 |
498 | def donate(request, template_name='donate.html'):
499 | return render(request, template_name)
500 |
501 | def help(request, template_name='help.html'):
502 | return render(request, template_name)
503 |
504 | def handler500(request, template_name='500.html'):
505 | response = render(request, template_name)
506 | response.status_code = 500
507 | return render(request, template_name)
508 |
--------------------------------------------------------------------------------
/apps/codrspace/static/bootstrap.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Bootstrap.js by @fat & @mdo
3 | * Copyright 2012 Twitter, Inc.
4 | * http://www.apache.org/licenses/LICENSE-2.0.txt
5 | */
6 | !function(a){a(function(){"use strict",a.support.transition=function(){var b=document.body||document.documentElement,c=b.style,d=c.transition!==undefined||c.WebkitTransition!==undefined||c.MozTransition!==undefined||c.MsTransition!==undefined||c.OTransition!==undefined;return d&&{end:function(){var b="TransitionEnd";return a.browser.webkit?b="webkitTransitionEnd":a.browser.mozilla?b="transitionend":a.browser.opera&&(b="oTransitionEnd"),b}()}}()})}(window.jQuery),!function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype={constructor:c,close:function(b){function f(){e.trigger("closed").remove()}var c=a(this),d=c.attr("data-target"),e;d||(d=c.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),e=a(d),e.trigger("close"),b&&b.preventDefault(),e.length||(e=c.hasClass("alert")?c:c.parent()),e.trigger("close").removeClass("in"),a.support.transition&&e.hasClass("fade")?e.on(a.support.transition.end,f):f()}},a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("alert");e||d.data("alert",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.alert.Constructor=c,a(function(){a("body").on("click.alert.data-api",b,c.prototype.close)})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.button.defaults,c)};b.prototype={constructor:b,setState:function(a){var b="disabled",c=this.$element,d=c.data(),e=c.is("input")?"val":"html";a+="Text",d.resetText||c.data("resetText",c[e]()),c[e](d[a]||this.options[a]),setTimeout(function(){a=="loadingText"?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},toggle:function(){var a=this.$element.parent('[data-toggle="buttons-radio"]');a&&a.find(".active").removeClass("active"),this.$element.toggleClass("active")}},a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("button"),f=typeof c=="object"&&c;e||d.data("button",e=new b(this,f)),c=="toggle"?e.toggle():c&&e.setState(c)})},a.fn.button.defaults={loadingText:"loading..."},a.fn.button.Constructor=b,a(function(){a("body").on("click.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle")})})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.carousel.defaults,c),this.options.slide&&this.slide(this.options.slide),this.options.pause=="hover"&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.prototype={cycle:function(){return this.interval=setInterval(a.proxy(this.next,this),this.options.interval),this},to:function(b){var c=this.$element.find(".active"),d=c.parent().children(),e=d.index(c),f=this;if(b>d.length-1||b<0)return;return this.sliding?this.$element.one("slid",function(){f.to(b)}):e==b?this.pause().cycle():this.slide(b>e?"next":"prev",a(d[b]))},pause:function(){return clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(b,c){var d=this.$element.find(".active"),e=c||d[b](),f=this.interval,g=b=="next"?"left":"right",h=b=="next"?"first":"last",i=this;this.sliding=!0,f&&this.pause(),e=e.length?e:this.$element.find(".item")[h]();if(e.hasClass("active"))return;return!a.support.transition&&this.$element.hasClass("slide")?(this.$element.trigger("slide"),d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")):(e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),this.$element.trigger("slide"),this.$element.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)})),f&&this.cycle(),this}},a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("carousel"),f=typeof c=="object"&&c;e||d.data("carousel",e=new b(this,f)),typeof c=="number"?e.to(c):typeof c=="string"||(c=f.slide)?e[c]():e.cycle()})},a.fn.carousel.defaults={interval:5e3,pause:"hover"},a.fn.carousel.Constructor=b,a(function(){a("body").on("click.carousel.data-api","[data-slide]",function(b){var c=a(this),d,e=a(c.attr("data-target")||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,"")),f=!e.data("modal")&&a.extend({},e.data(),c.data());e.carousel(f),b.preventDefault()})})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.collapse.defaults,c),this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.prototype={constructor:b,dimension:function(){var a=this.$element.hasClass("width");return a?"width":"height"},show:function(){var b=this.dimension(),c=a.camelCase(["scroll",b].join("-")),d=this.$parent&&this.$parent.find(".in"),e;d&&d.length&&(e=d.data("collapse"),d.collapse("hide"),e||d.data("collapse",null)),this.$element[b](0),this.transition("addClass","show","shown"),this.$element[b](this.$element[0][c])},hide:function(){var a=this.dimension();this.reset(this.$element[a]()),this.transition("removeClass","hide","hidden"),this.$element[a](0)},reset:function(a){var b=this.dimension();return this.$element.removeClass("collapse")[b](a||"auto")[0].offsetWidth,this.$element[a?"addClass":"removeClass"]("collapse"),this},transition:function(b,c,d){var e=this,f=function(){c=="show"&&e.reset(),e.$element.trigger(d)};this.$element.trigger(c)[b]("in"),a.support.transition&&this.$element.hasClass("collapse")?this.$element.one(a.support.transition.end,f):f()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}},a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("collapse"),f=typeof c=="object"&&c;e||d.data("collapse",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.collapse.defaults={toggle:!0},a.fn.collapse.Constructor=b,a(function(){a("body").on("click.collapse.data-api","[data-toggle=collapse]",function(b){var c=a(this),d,e=c.attr("data-target")||b.preventDefault()||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""),f=a(e).data("collapse")?"toggle":c.data();a(e).collapse(f)})})}(window.jQuery),!function(a){function d(){a(b).parent().removeClass("open")}"use strict";var b='[data-toggle="dropdown"]',c=function(b){var c=a(b).on("click.dropdown.data-api",this.toggle);a("html").on("click.dropdown.data-api",function(){c.parent().removeClass("open")})};c.prototype={constructor:c,toggle:function(b){var c=a(this),e=c.attr("data-target"),f,g;return e||(e=c.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,"")),f=a(e),f.length||(f=c.parent()),g=f.hasClass("open"),d(),!g&&f.toggleClass("open"),!1}},a.fn.dropdown=function(b){return this.each(function(){var d=a(this),e=d.data("dropdown");e||d.data("dropdown",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.dropdown.Constructor=c,a(function(){a("html").on("click.dropdown.data-api",d),a("body").on("click.dropdown.data-api",b,c.prototype.toggle)})}(window.jQuery),!function(a){function c(){var b=this,c=setTimeout(function(){b.$element.off(a.support.transition.end),d.call(b)},500);this.$element.one(a.support.transition.end,function(){clearTimeout(c),d.call(b)})}function d(a){this.$element.hide().trigger("hidden"),e.call(this)}function e(b){var c=this,d=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var e=a.support.transition&&d;this.$backdrop=a('').appendTo(document.body),this.options.backdrop!="static"&&this.$backdrop.click(a.proxy(this.hide,this)),e&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),e?this.$backdrop.one(a.support.transition.end,b):b()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(a.support.transition.end,a.proxy(f,this)):f.call(this)):b&&b()}function f(){this.$backdrop.remove(),this.$backdrop=null}function g(){var b=this;this.isShown&&this.options.keyboard?a(document).on("keyup.dismiss.modal",function(a){a.which==27&&b.hide()}):this.isShown||a(document).off("keyup.dismiss.modal")}"use strict";var b=function(b,c){this.options=c,this.$element=a(b).delegate('[data-dismiss="modal"]',"click.dismiss.modal",a.proxy(this.hide,this))};b.prototype={constructor:b,toggle:function(){return this[this.isShown?"hide":"show"]()},show:function(){var b=this;if(this.isShown)return;a("body").addClass("modal-open"),this.isShown=!0,this.$element.trigger("show"),g.call(this),e.call(this,function(){var c=a.support.transition&&b.$element.hasClass("fade");!b.$element.parent().length&&b.$element.appendTo(document.body),b.$element.show(),c&&b.$element[0].offsetWidth,b.$element.addClass("in"),c?b.$element.one(a.support.transition.end,function(){b.$element.trigger("shown")}):b.$element.trigger("shown")})},hide:function(b){b&&b.preventDefault();if(!this.isShown)return;var e=this;this.isShown=!1,a("body").removeClass("modal-open"),g.call(this),this.$element.trigger("hide").removeClass("in"),a.support.transition&&this.$element.hasClass("fade")?c.call(this):d.call(this)}},a.fn.modal=function(c){return this.each(function(){var d=a(this),e=d.data("modal"),f=a.extend({},a.fn.modal.defaults,d.data(),typeof c=="object"&&c);e||d.data("modal",e=new b(this,f)),typeof c=="string"?e[c]():f.show&&e.show()})},a.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},a.fn.modal.Constructor=b,a(function(){a("body").on("click.modal.data-api",'[data-toggle="modal"]',function(b){var c=a(this),d,e=a(c.attr("data-target")||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,"")),f=e.data("modal")?"toggle":a.extend({},e.data(),c.data());b.preventDefault(),e.modal(f)})})}(window.jQuery),!function(a){"use strict";var b=function(a,b){this.init("tooltip",a,b)};b.prototype={constructor:b,init:function(b,c,d){var e,f;this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.enabled=!0,this.options.trigger!="manual"&&(e=this.options.trigger=="hover"?"mouseenter":"focus",f=this.options.trigger=="hover"?"mouseleave":"blur",this.$element.on(e,this.options.selector,a.proxy(this.enter,this)),this.$element.on(f,this.options.selector,a.proxy(this.leave,this))),this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(b){return b=a.extend({},a.fn[this.type].defaults,b,this.$element.data()),b.delay&&typeof b.delay=="number"&&(b.delay={show:b.delay,hide:b.delay}),b},enter:function(b){var c=a(b.currentTarget)[this.type](this._options).data(this.type);!c.options.delay||!c.options.delay.show?c.show():(c.hoverState="in",setTimeout(function(){c.hoverState=="in"&&c.show()},c.options.delay.show))},leave:function(b){var c=a(b.currentTarget)[this.type](this._options).data(this.type);!c.options.delay||!c.options.delay.hide?c.hide():(c.hoverState="out",setTimeout(function(){c.hoverState=="out"&&c.hide()},c.options.delay.hide))},show:function(){var a,b,c,d,e,f,g;if(this.hasContent()&&this.enabled){a=this.tip(),this.setContent(),this.options.animation&&a.addClass("fade"),f=typeof this.options.placement=="function"?this.options.placement.call(this,a[0],this.$element[0]):this.options.placement,b=/in/.test(f),a.remove().css({top:0,left:0,display:"block"}).appendTo(b?this.$element:document.body),c=this.getPosition(b),d=a[0].offsetWidth,e=a[0].offsetHeight;switch(b?f.split(" ")[1]:f){case"bottom":g={top:c.top+c.height,left:c.left+c.width/2-d/2};break;case"top":g={top:c.top-e,left:c.left+c.width/2-d/2};break;case"left":g={top:c.top+c.height/2-e/2,left:c.left-d};break;case"right":g={top:c.top+c.height/2-e/2,left:c.left+c.width}}a.css(g).addClass(f).addClass("in")}},setContent:function(){var a=this.tip();a.find(".tooltip-inner").html(this.getTitle()),a.removeClass("fade in top bottom left right")},hide:function(){function d(){var b=setTimeout(function(){c.off(a.support.transition.end).remove()},500);c.one(a.support.transition.end,function(){clearTimeout(b),c.remove()})}var b=this,c=this.tip();c.removeClass("in"),a.support.transition&&this.$tip.hasClass("fade")?d():c.remove()},fixTitle:function(){var a=this.$element;(a.attr("title")||typeof a.attr("data-original-title")!="string")&&a.attr("data-original-title",a.attr("title")||"").removeAttr("title")},hasContent:function(){return this.getTitle()},getPosition:function(b){return a.extend({},b?{top:0,left:0}:this.$element.offset(),{width:this.$element[0].offsetWidth,height:this.$element[0].offsetHeight})},getTitle:function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||(typeof c.title=="function"?c.title.call(b[0]):c.title),a=(a||"").toString().replace(/(^\s*|\s*$)/,""),a},tip:function(){return this.$tip=this.$tip||a(this.options.template)},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(){this[this.tip().hasClass("in")?"hide":"show"]()}},a.fn.tooltip=function(c){return this.each(function(){var d=a(this),e=d.data("tooltip"),f=typeof c=="object"&&c;e||d.data("tooltip",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.tooltip.Constructor=b,a.fn.tooltip.defaults={animation:!0,delay:0,selector:!1,placement:"top",trigger:"hover",title:"",template:''}}(window.jQuery),!function(a){"use strict";var b=function(a,b){this.init("popover",a,b)};b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype,{constructor:b,setContent:function(){var b=this.tip(),c=this.getTitle(),d=this.getContent();b.find(".popover-title")[a.type(c)=="object"?"append":"html"](c),b.find(".popover-content > *")[a.type(d)=="object"?"append":"html"](d),b.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var a,b=this.$element,c=this.options;return a=b.attr("data-content")||(typeof c.content=="function"?c.content.call(b[0]):c.content),a=a.toString().replace(/(^\s*|\s*$)/,""),a},tip:function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip}}),a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("popover"),f=typeof c=="object"&&c;e||d.data("popover",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.defaults=a.extend({},a.fn.tooltip.defaults,{placement:"right",content:"",template:''})}(window.jQuery),!function(a){function b(b,c){var d=a.proxy(this.process,this),e=a(b).is("body")?a(window):a(b),f;this.options=a.extend({},a.fn.scrollspy.defaults,c),this.$scrollElement=e.on("scroll.scroll.data-api",d),this.selector=(this.options.target||(f=a(b).attr("href"))&&f.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=a("body").on("click.scroll.data-api",this.selector,d),this.refresh(),this.process()}"use strict",b.prototype={constructor:b,refresh:function(){this.targets=this.$body.find(this.selector).map(function(){var b=a(this).attr("href");return/^#\w/.test(b)&&a(b).length?b:null}),this.offsets=a.map(this.targets,function(b){return a(b).position().top})},process:function(){var a=this.$scrollElement.scrollTop()+this.options.offset,b=this.offsets,c=this.targets,d=this.activeTarget,e;for(e=b.length;e--;)d!=c[e]&&a>=b[e]&&(!b[e+1]||a<=b[e+1])&&this.activate(c[e])},activate:function(a){var b;this.activeTarget=a,this.$body.find(this.selector).parent(".active").removeClass("active"),b=this.$body.find(this.selector+'[href="'+a+'"]').parent("li").addClass("active"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active")}},a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("scrollspy"),f=typeof c=="object"&&c;e||d.data("scrollspy",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.defaults={offset:10},a(function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(window.jQuery),!function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype={constructor:b,show:function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.attr("data-target"),e,f;d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,""));if(b.parent("li").hasClass("active"))return;e=c.find(".active a").last()[0],b.trigger({type:"show",relatedTarget:e}),f=a(d),this.activate(b.parent("li"),c),this.activate(f,f.parent(),function(){b.trigger({type:"shown",relatedTarget:e})})},activate:function(b,c,d){function g(){e.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),f?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var e=c.find("> .active"),f=d&&a.support.transition&&e.hasClass("fade");f?e.one(a.support.transition.end,g):g(),e.removeClass("in")}},a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("tab");e||d.data("tab",e=new b(this)),typeof c=="string"&&e[c]()})},a.fn.tab.Constructor=b,a(function(){a("body").on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})})}(window.jQuery),!function(a){"use strict";var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.typeahead.defaults,c),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.$menu=a(this.options.menu).appendTo("body"),this.source=this.options.source,this.shown=!1,this.listen()};b.prototype={constructor:b,select:function(){var a=this.$menu.find(".active").attr("data-value");return this.$element.val(a),this.$element.change(),this.hide()},show:function(){var b=a.extend({},this.$element.offset(),{height:this.$element[0].offsetHeight});return this.$menu.css({top:b.top+b.height,left:b.left}),this.$menu.show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(b){var c=this,d,e;return this.query=this.$element.val(),this.query?(d=a.grep(this.source,function(a){if(c.matcher(a))return a}),d=this.sorter(d),d.length?this.render(d.slice(0,this.options.items)).show():this.shown?this.hide():this):this.shown?this.hide():this},matcher:function(a){return~a.toLowerCase().indexOf(this.query.toLowerCase())},sorter:function(a){var b=[],c=[],d=[],e;while(e=a.shift())e.toLowerCase().indexOf(this.query.toLowerCase())?~e.indexOf(this.query)?c.push(e):d.push(e):b.push(e);return b.concat(c,d)},highlighter:function(a){return a.replace(new RegExp("("+this.query+")","ig"),function(a,b){return""+b+""})},render:function(b){var c=this;return b=a(b).map(function(b,d){return b=a(c.options.item).attr("data-value",d),b.find("a").html(c.highlighter(d)),b[0]}),b.first().addClass("active"),this.$menu.html(b),this},next:function(b){var c=this.$menu.find(".active").removeClass("active"),d=c.next();d.length||(d=a(this.$menu.find("li")[0])),d.addClass("active")},prev:function(a){var b=this.$menu.find(".active").removeClass("active"),c=b.prev();c.length||(c=this.$menu.find("li").last()),c.addClass("active")},listen:function(){this.$element.on("blur",a.proxy(this.blur,this)).on("keypress",a.proxy(this.keypress,this)).on("keyup",a.proxy(this.keyup,this)),(a.browser.webkit||a.browser.msie)&&this.$element.on("keydown",a.proxy(this.keypress,this)),this.$menu.on("click",a.proxy(this.click,this)).on("mouseenter","li",a.proxy(this.mouseenter,this))},keyup:function(a){switch(a.keyCode){case 40:case 38:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}a.stopPropagation(),a.preventDefault()},keypress:function(a){if(!this.shown)return;switch(a.keyCode){case 9:case 13:case 27:a.preventDefault();break;case 38:a.preventDefault(),this.prev();break;case 40:a.preventDefault(),this.next()}a.stopPropagation()},blur:function(a){var b=this;setTimeout(function(){b.hide()},150)},click:function(a){a.stopPropagation(),a.preventDefault(),this.select()},mouseenter:function(b){this.$menu.find(".active").removeClass("active"),a(b.currentTarget).addClass("active")}},a.fn.typeahead=function(c){return this.each(function(){var d=a(this),e=d.data("typeahead"),f=typeof c=="object"&&c;e||d.data("typeahead",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.typeahead.defaults={source:[],items:8,menu:'',item:' '},a.fn.typeahead.Constructor=b,a(function(){a("body").on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(b){var c=a(this);if(c.data("typeahead"))return;b.preventDefault(),c.typeahead(c.data())})})}(window.jQuery);
--------------------------------------------------------------------------------