{{ post.content|explosivo }}
26 | {% else %} 27 | {% cache 3600 content post.pk %} 28 |{{ post.content|explosivo }}
29 | {% endcache %} 30 | {% endif %} 31 |├── .gitignore ├── Dockerfile ├── INSTALL.md ├── LICENSE ├── README.md ├── __init__.py ├── apps └── codrspace │ ├── __init__.py │ ├── api.py │ ├── backend.py │ ├── context_processors.py │ ├── feeds.py │ ├── forms.py │ ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── create_api_keys.py │ ├── managers.py │ ├── mock_views.py │ ├── models.py │ ├── pygments │ ├── __init__.py │ └── styles │ │ ├── __init__.py │ │ ├── github.py │ │ └── solarized.py │ ├── site_maps.py │ ├── static │ ├── anytimec.css │ ├── anytimec.js │ ├── bootstrap-alert.js │ ├── bootstrap-dropdown.js │ ├── bootstrap-modal.js │ ├── bootstrap-popover.js │ ├── bootstrap-tooltip.js │ ├── bootstrap.min.css │ ├── bootstrap.min.js │ ├── codrspace-logo-small.png │ ├── codrspace-nav-logo.png │ ├── codrspace.css │ ├── codrspace.js │ ├── codrspace_admin.js │ ├── favicon.ico │ ├── glyphicons-halflings-white.png │ ├── glyphicons-halflings.png │ ├── jquery.hotkeys.js │ ├── jquery.insertat.js │ ├── jquery.labelify.js │ ├── jquery.min.js │ ├── list-check.png │ ├── pygments.css │ ├── underline.png │ ├── wmd-buttons.png │ ├── wmd.css │ └── wmd.js │ ├── templates │ ├── 404.html │ ├── 500.html │ ├── _form_messages.html │ ├── _messages.html │ ├── _nav.html │ ├── _nav_auth.html │ ├── _nav_shutdown.html │ ├── _partial-media-list.html │ ├── _partial-post-list.html │ ├── _post_content.html │ ├── _shortcode_help.html │ ├── _user_detail.html │ ├── add.html │ ├── api_settings.html │ ├── auth_base.html │ ├── auth_error.html │ ├── base.html │ ├── delete.html │ ├── donate.html │ ├── edit.html │ ├── feedback.html │ ├── feeds │ │ └── post_description.html │ ├── help.html │ ├── home.html │ ├── home_shutdown.html │ ├── lastest_posts.html │ ├── post_detail.html │ ├── post_download.html │ ├── post_list.html │ ├── post_list_shutdown.html │ ├── preview.html │ ├── recent_codrs.html │ ├── settings.html │ └── top_posters.html │ ├── templatetags │ ├── __init__.py │ ├── codrspace_tags.py │ ├── short_codes.py │ └── syntax_color.py │ ├── tests.py │ ├── urls.py │ ├── utils.py │ └── views.py ├── deploy ├── __init__.py └── wsgi.py ├── docker-compose.yml ├── example_local_settings.py ├── manage.py ├── requirements.pip ├── requirements_dev.pip ├── settings.py └── urls.py /.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 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:2.7.14-slim 2 | 3 | RUN apt-get update && \ 4 | apt-get install -y git-core && \ 5 | apt-get install -y libmysqlclient-dev && \ 6 | apt-get install -y build-essential 7 | 8 | ADD requirements_dev.pip /tmp/requirements_dev.pip 9 | RUN pip install -r /tmp/requirements_dev.pip && rm /tmp/requirements_dev.pip 10 | 11 | WORKDIR /code/codrspace_app -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # Installation Instruction 2 | 3 | The following steps are required to install a local verison of codrspace.com. 4 | You can see the full hosted solution on EC2 at http://codrspace.com. 5 | Please keep in mind that there are a few limitations to running just the local 6 | version: 7 | 8 | 1. We are using Github OAuth for signup/authentication, which only allows a 9 | single callback url for the final part of the OAuth process. Thus, this 10 | callback url is set to the hosted version and faked on a local development version. 11 | 2. You will not be able to have a nice profile and missing a lot of Github 12 | integration if you run locally without an external Internet connection. 13 | Please connect to the Internet :) 14 | 15 | **See section on 'Running project' for information on how to run both instances locally** 16 | 17 | ## General environment setup 18 | 19 | ### Non-virtualenv install 20 | 21 | - Install python 2.6 or 2.7 `http://www.python.org` 22 | - Install setuptools 23 | - Download project from: `http://pypi.python.org/pypi/setuptools#files` 24 | - Run the following: `sh setuptools-0.6c9-py2.4.egg` (replace with egg file from above) 25 | - install pip 26 | - `easy_install pip` 27 | - Clone project 28 | - `git clone git@github.com:Codrspace/codrspace.git codrspace_app` 29 | - Use pip to install all project dependencies 30 | - `pip install -r requirements_dev.pip` (requirements file is in root of project) 31 | 32 | ### Install with virtualenv 33 | 34 | - Install python 2.6 or 2.7 `http://www.python.org` 35 | - Install virtualenv 36 | - `pip install virtualenv` 37 | - `pip install virtualenvwrapper` 38 | - Add the following lines to your .bashrc and restart your shell 39 | 40 | - `export WORKON_HOME=$HOME/.virtualenv` 41 | - `source /usr/local/bin/virtualenvwrapper.sh` 42 | - `export PIP_VIRTUALENV_BASE=$WORKON_HOME` 43 | - `export PIP_RESPECT_VIRTUALENV=true` 44 | 45 | - Make a virtualenv called `codrspace_app` 46 | - `mkvirtualenv codrspace_app` 47 | - Activate the virtual environment 48 | - `workon codrspace_app` 49 | - Clone project 50 | - `git clone git@github.com:Codrspace/codrspace.git codrspace_app` 51 | - Use requirements file to install all project libraries: 52 | - `pip install -r requirements_dev.pip` 53 | 54 | ### Running project locally after environment setup 55 | 56 | Due to limitation #1, we have developed a solution to 'fake' out the OAuth 57 | callbacks for local testing. Unfortunately it requires running two Django dev servers. 58 | 59 | 1. Clone the project, copy the example local_settings, start the server on port 9000 for oAuth. 60 | 61 | - `git clone git@github.com:Codrspace/codrspace.git codrspace_app` 62 | - `cd codrspace_app` 63 | - `workon codrspace_app` (only if you used the virtualenv route) 64 | - `cp example_local_settings.py local_settings.py` 65 | - set `username` key in `GITHUB_AUTH` in your `local_settings.py` to your github username 66 | - `python manage.py syncdb` 67 | - `python manage.py runserver 9000` 68 | 69 | 2. Open another shell and start the dev server on port 8000 for the site. 70 | 71 | - `cd codrspace_app` 72 | - `workon codrspace` (only if you used the virtualenv route) 73 | - `python manage.py runserver ` 74 | 75 | Now you have two instances of the django development server running. 76 | The instance on port 9000 is only for fake oAuth validation. 77 | 78 | Use the site as you normally would through `http://localhost:8000`. 79 | 80 | ### Setting up API keys 81 | 82 | - Manually apply the following patch to tastypie: 83 | [tastypie bugfix](https://github.com/toastdriven/django-tastypie/commit/520b33f) 84 | 85 | - Now you have a few choices: 86 | 1. Delete your database and run `python manage.py syncdb` again 87 | 2. Back-fill any existing users with api keys by running: 88 | - `python manage.py backfill_api_keys` 89 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | License: MIT 2 | Copyright (c) 2011 Codrspace, http://codrspace.com 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CodrSpace 2 | 3 | [http://codrspace.com](http://codrspace.com) 4 | 5 | ## About 6 | 7 | CoderSpace is a blogging platform for coders. It spawned from the Django Dash 2011 competition and is currently 8 | maintained by [Glen Zangirolami](https://github.com/glenbot) and [Luke Lee](https://github.com/durden). 9 | 10 | It is integrated with GitHub so you can talk and write about code that exists on Github. 11 | 12 | ## Contribute 13 | 14 | If you would like to contribute to the project, fork this repository, and follow the 15 | [installation instructions](https://github.com/Codrspace/codrspace/blob/master/INSTALL.md). 16 | 17 | ## License 18 | 19 | Codrspace is licensed unser the Open Source MIT License 20 | [See LICENSE](https://github.com/Codrspace/codrspace/blob/master/LICENSE) 21 | 22 | ## Issues/Features/Todo 23 | 24 | Please use the [CodrSpace GitHub issue tracker](https://github.com/Codrspace/codrspace/issues) to make any pull, feature, or issue requests. 25 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codrspace/codrspace/36a409ffe7a06a8844c290235e552ad201a9a58a/__init__.py -------------------------------------------------------------------------------- /apps/codrspace/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codrspace/codrspace/36a409ffe7a06a8844c290235e552ad201a9a58a/apps/codrspace/__init__.py -------------------------------------------------------------------------------- /apps/codrspace/api.py: -------------------------------------------------------------------------------- 1 | from django import VERSION 2 | from django.contrib.auth.models import User 3 | from django.core.exceptions import MultipleObjectsReturned 4 | 5 | from tastypie.resources import ModelResource 6 | from tastypie.authentication import ApiKeyAuthentication 7 | from tastypie.authorization import Authorization 8 | from tastypie.serializers import Serializer 9 | from tastypie.validation import CleanedDataFormValidation 10 | from tastypie import fields 11 | from tastypie.exceptions import NotFound, Unauthorized 12 | from tastypie.utils import dict_strip_unicode_keys 13 | from tastypie import http 14 | from tastypie.constants import ALL 15 | 16 | from codrspace.forms import APIPostForm 17 | from codrspace.models import Post 18 | 19 | 20 | class CodrspaceModelResource(ModelResource): 21 | def put_detail(self, request, **kwargs): 22 | """Override put_detail so that it doesn't create a new resource when 23 | One doesn't exists, I don't like this reflex""" 24 | if VERSION >= (1, 4): 25 | body = request.body 26 | else: 27 | body = request.raw_post_data 28 | deserialized = self.deserialize(request, body, format=request.META.get('CONTENT_TYPE', 'application/json')) 29 | deserialized = self.alter_deserialized_detail_data(request, deserialized) 30 | bundle = self.build_bundle(data=dict_strip_unicode_keys(deserialized), request=request) 31 | 32 | try: 33 | updated_bundle = self.obj_update(bundle=bundle, **self.remove_api_resource_names(kwargs)) 34 | 35 | if not self._meta.always_return_data: 36 | return http.HttpNoContent() 37 | else: 38 | updated_bundle = self.full_dehydrate(updated_bundle) 39 | updated_bundle = self.alter_detail_data_to_serialize(request, updated_bundle) 40 | return self.create_response(request, updated_bundle, response_class=http.HttpAccepted) 41 | except (NotFound, MultipleObjectsReturned): 42 | raise NotFound("A model instance matching the provided arguments could not be found.") 43 | 44 | 45 | class CodrspaceAuthorization(Authorization): 46 | """Authorization based on request user""" 47 | def delete_list(self, object_list, bundle): 48 | raise Unauthorized('Cannot delete a list of posts') 49 | 50 | def delete_detail(self, object_list, bundle): 51 | if bundle.obj.author == bundle.request.user: 52 | return True 53 | return False 54 | 55 | def read_list(self, object_list, bundle): 56 | return object_list.filter(author=bundle.request.user) 57 | 58 | def read_detail(self, object_list, bundle): 59 | """read_detail will handle the update_detail permissions""" 60 | if bundle.obj.author == bundle.request.user: 61 | return True 62 | return False 63 | 64 | def update_list(self, object_list, bundle): 65 | raise Unauthorized('Cannot update a list of posts') 66 | 67 | 68 | class PostValidation(CleanedDataFormValidation): 69 | """Form validation for a post""" 70 | def is_valid(self, bundle, request=None): 71 | """Adds request.user to the form class before validation""" 72 | form_args = self.form_args(bundle) 73 | form_args.update({'user': request.user}) 74 | 75 | form = self.form_class(**form_args) 76 | 77 | if form.is_valid(): 78 | return {} 79 | 80 | # The data is invalid. Let's collect all the error messages & return them. 81 | return form.errors 82 | 83 | 84 | class PostResource(CodrspaceModelResource): 85 | # make sure these don't get set. The Django 86 | # model system will take care of it 87 | create_dt = fields.DateTimeField(attribute='create_dt', readonly=True) 88 | update_dt = fields.DateTimeField(attribute='update_dt', readonly=True) 89 | 90 | class Meta: 91 | resource_name = 'post' 92 | queryset = Post.objects.all() 93 | queryset = queryset.order_by('-publish_dt') 94 | allowed_methods = ['get', 'put', 'delete', 'post'] 95 | authentication = ApiKeyAuthentication() 96 | authorization = CodrspaceAuthorization() 97 | serializer = Serializer(formats=['json']) 98 | validation = PostValidation(form_class=APIPostForm) 99 | always_return_data = True 100 | filtering = {"slug": ALL} 101 | 102 | def dehydrate(self, bundle): 103 | bundle.data['url'] = bundle.obj.url() 104 | return bundle 105 | 106 | def hydrate(self, bundle, request=None): 107 | # set the status if it is not specified 108 | if not bundle.obj.status: 109 | bundle.obj.status = "draft" 110 | 111 | # automatically set the author 112 | bundle.obj.author = User.objects.get(pk=bundle.request.user.id) 113 | 114 | return bundle 115 | -------------------------------------------------------------------------------- /apps/codrspace/backend.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | 3 | 4 | class ModelBackend(object): 5 | """ 6 | Custom authentiction backend 7 | """ 8 | supports_object_permissions = True 9 | supports_anonymous_user = True 10 | 11 | def authenticate(self, username=None, password=None, user=None): 12 | """ 13 | Modified version of django's authenticate. 14 | 15 | Will accept a user object, bypassing the password check. 16 | Returns the user for auto_login purposes 17 | """ 18 | if user: 19 | if hasattr(user, 'auto_login'): 20 | if not user.is_anonymous() and user.auto_login: 21 | return user 22 | else: 23 | try: 24 | user = User.objects.get(username=username) 25 | if user.check_password(password): 26 | return user 27 | except User.DoesNotExist: 28 | return None 29 | 30 | return None 31 | 32 | def get_user(self, user_id): 33 | try: 34 | return User.objects.get(pk=user_id) 35 | except User.DoesNotExist: 36 | return None 37 | -------------------------------------------------------------------------------- /apps/codrspace/context_processors.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.core.cache import cache 3 | 4 | from codrspace.models import SystemNotification 5 | 6 | 7 | def get_system_notifications(): 8 | notifications = cache.get('system_notifications') 9 | 10 | if not notifications: 11 | notifications = SystemNotification.objects.filter(enabled=True) 12 | cache.set('system_notifications', notifications, 86400) 13 | 14 | return notifications 15 | 16 | 17 | def codrspace_contexts(request): 18 | """ 19 | All custom context vars for codrspace 20 | """ 21 | contexts = {} 22 | 23 | # add SITE_TAGLINE, and SITE_NAME, and VERSION to the context 24 | contexts.update({'SITE_TAGLINE': settings.SITE_TAGLINE}) 25 | contexts.update({'SITE_NAME': settings.SITE_NAME}) 26 | contexts.update({'VERSION': settings.VERSION}) 27 | contexts.update({'ANALYTICS_CODE': settings.ANALYTICS_CODE}) 28 | contexts.update({'SITE_URL': settings.SITE_URL}) 29 | contexts.update({'SYSTEM_NOTIFICATIONS': get_system_notifications() }) 30 | 31 | return contexts 32 | -------------------------------------------------------------------------------- /apps/codrspace/feeds.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from django.contrib.syndication.views import Feed 3 | from django.shortcuts import get_object_or_404 4 | from django.contrib.auth.models import User 5 | from codrspace.models import Post 6 | 7 | 8 | class LatestPostsFeed(Feed): 9 | description_template = 'feeds/post_description.html' 10 | 11 | def get_object(self, request, username): 12 | return get_object_or_404(User, username=username) 13 | 14 | def title(self, obj): 15 | return 'Codrspace: Posts by %s' % obj 16 | 17 | def link(self, obj): 18 | return '/%s/feed/' % obj 19 | 20 | def description(self, obj): 21 | return "Codrspace: All posts authored by %s" % obj 22 | 23 | def items(self, user): 24 | posts = Post.objects.filter( 25 | publish_dt__lte=datetime.now(), 26 | status='published', 27 | author=user, 28 | ) 29 | return posts.order_by('-publish_dt')[:5] 30 | 31 | def item_title(self, item): 32 | return item.title or 'Untitled' 33 | 34 | def item_description(self, item): 35 | return item.content 36 | 37 | def item_pubdate(self, item): 38 | return item.publish_dt 39 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /apps/codrspace/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codrspace/codrspace/36a409ffe7a06a8844c290235e552ad201a9a58a/apps/codrspace/management/__init__.py -------------------------------------------------------------------------------- /apps/codrspace/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codrspace/codrspace/36a409ffe7a06a8844c290235e552ad201a9a58a/apps/codrspace/management/commands/__init__.py -------------------------------------------------------------------------------- /apps/codrspace/management/commands/create_api_keys.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand, CommandError 2 | from django.contrib.auth.models import User 3 | from django.utils import translation 4 | from tastypie.models import ApiKey 5 | 6 | 7 | class Command(BaseCommand): 8 | """Create api keys for existing users""" 9 | 10 | def handle(self, *args, **options): 11 | users = User.objects.all() 12 | 13 | for user in users: 14 | print "Creating api key for %s" % user 15 | ApiKey.objects.create(user=user) 16 | -------------------------------------------------------------------------------- /apps/codrspace/managers.py: -------------------------------------------------------------------------------- 1 | from django.db.models import Manager 2 | from django.core.cache import cache 3 | 4 | 5 | class SettingManager(Manager): 6 | """ 7 | Manager model for settings 8 | """ 9 | def get(self, *args, **kwargs): 10 | user_pk = None 11 | user_settings = None 12 | cache_key = None 13 | 14 | # get user information if passed 15 | if 'user' in kwargs: 16 | user = kwargs['user'] 17 | if not user.is_anonymous(): 18 | user_pk = kwargs['user'].pk 19 | 20 | # set a cache key for this user 21 | if user_pk: 22 | cache_key = '%s_user_settings' % user_pk 23 | user_settings = cache.get(cache_key) 24 | 25 | if not user_settings: 26 | user_settings = super(SettingManager, self).get(*args, **kwargs) 27 | cache.set(cache_key, user_settings) 28 | else: 29 | user_settings = super(SettingManager, self).get(*args, **kwargs) 30 | 31 | return user_settings 32 | -------------------------------------------------------------------------------- /apps/codrspace/mock_views.py: -------------------------------------------------------------------------------- 1 | """Mock views for testing OAuth with Github API locally""" 2 | 3 | 4 | from django.http import HttpResponse 5 | from django.core.urlresolvers import reverse 6 | from django.shortcuts import redirect 7 | from django.conf import settings 8 | 9 | 10 | def fake_user(request): 11 | """Fake user data for the user""" 12 | 13 | if not hasattr(settings, 'GITHUB_USER_JSON'): 14 | raise Exception('GITHUB_USER_JSON must be set for fake user data') 15 | 16 | if not settings.GITHUB_USER_JSON: 17 | raise Exception('GITHUB_USER_JSON must contain github user JSON data') 18 | 19 | return HttpResponse(settings.GITHUB_USER_JSON, mimetype="application/json", status=200) 20 | 21 | 22 | def authorize(request): 23 | """Fake calling authorize for github api""" 24 | 25 | if 'client_id' not in request.GET: 26 | raise Exception("Authorize must specify client_id") 27 | 28 | # Assume 200 and debugging github url 29 | code = 200 30 | callback_url = settings.GITHUB_AUTH['callback_url'] 31 | 32 | return redirect("%s?code=%d" % (callback_url, code)) 33 | 34 | 35 | def access_token(request): 36 | """Fake calling method to get access token for github api""" 37 | 38 | if request.method != 'POST': 39 | raise Exception("Must use POST request") 40 | 41 | if 'client_id' not in request.POST: 42 | raise Exception("Authorize must specify client_id") 43 | 44 | if 'client_secret' not in request.POST: 45 | raise Exception("Authorize must specify client_secret") 46 | 47 | if 'code' not in request.POST: 48 | raise Exception("Authorize must specify code") 49 | 50 | # Just using junk for token b/c it won't be used locally, just need to get 51 | # the flow 52 | token = '2341342fdsffkjl234' 53 | return HttpResponse("access_token=%s&token_type=bearer" % (token), 54 | mimetype="text/plain") 55 | -------------------------------------------------------------------------------- /apps/codrspace/models.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import uuid 4 | 5 | from django.db import models 6 | from django.contrib.auth.models import User 7 | from django.utils.hashcompat import md5_constructor 8 | from django.core.cache import cache 9 | from django.utils.http import urlquote 10 | from django.conf import settings 11 | from timezones.fields import TimeZoneField 12 | from tastypie.models import create_api_key 13 | from codrspace.managers import SettingManager 14 | 15 | models.signals.post_save.connect(create_api_key, sender=User) 16 | 17 | STATUS_CHOICES = ( 18 | ('draft', 'Draft'), 19 | ('published', 'Published'), 20 | ) 21 | 22 | 23 | def invalidate_cache_key(fragment_name, *variables): 24 | args = md5_constructor(u':'.join([urlquote(var) for var in variables])) 25 | cache_key = 'template.cache.%s.%s' % (fragment_name, args.hexdigest()) 26 | cache.delete(cache_key) 27 | 28 | 29 | def file_directory(instance, filename): 30 | filename = re.sub(r'[^a-zA-Z0-9._]+', '-', filename) 31 | filename, ext = os.path.splitext(filename) 32 | return '%s%s' % (uuid.uuid1().hex[:13], ext) 33 | 34 | 35 | class Post(models.Model): 36 | title = models.CharField(max_length=200, blank=True) 37 | content = models.TextField(blank=True) 38 | slug = models.SlugField(max_length=75) 39 | author = models.ForeignKey(User, editable=False) 40 | comments = models.BooleanField(default=True, null=False) 41 | status = models.CharField(max_length=30, choices=STATUS_CHOICES, default=0) 42 | publish_dt = models.DateTimeField(null=True) 43 | create_dt = models.DateTimeField(auto_now_add=True) 44 | update_dt = models.DateTimeField(auto_now=True) 45 | 46 | class Meta: 47 | unique_together = ("slug", "author") 48 | 49 | def __unicode__(self): 50 | return '%s' % (self.title or 'Untitled') 51 | 52 | def url(self): 53 | return '%s%s' % (settings.SITE_URL, self.get_absolute_url(),) 54 | 55 | def save(self, *args, **kwargs): 56 | super(Post, self).save(*args, **kwargs) 57 | 58 | # Invalidate cache for all individual posts and the list of posts 59 | invalidate_cache_key('content', self.pk) 60 | 61 | @models.permalink 62 | def get_absolute_url(self): 63 | return ("post_detail", [self.author.username, self.slug]) 64 | 65 | 66 | class Media(models.Model): 67 | file = models.FileField(upload_to=file_directory, null=True) 68 | filename = models.CharField(max_length=200, editable=False) 69 | uploader = models.ForeignKey(User, editable=False) 70 | upload_dt = models.DateTimeField(auto_now_add=True) 71 | 72 | def type(self): 73 | ext = os.path.splitext(self.filename)[1].lower() 74 | # map file-type to extension 75 | types = { 76 | 'image': ('.jpg', '.jpeg', '.gif', '.png', '.tif', '.tiff', 77 | '.bmp',), 78 | 'text': ('.txt', '.doc', '.docx'), 79 | 'spreadsheet': ('.csv', '.xls', '.xlsx'), 80 | 'powerpoint': ('.ppt', '.pptx'), 81 | 'pdf': ('.pdf'), 82 | 'video': ('.wmv', '.mov', '.mpg', '.mp4', '.m4v'), 83 | 'zip': ('.zip'), 84 | 'code': ('.txt', '.py', '.htm', '.html', '.css', '.js', '.rb', '.sh'), 85 | } 86 | 87 | for type in types: 88 | if ext in types[type]: 89 | return type 90 | 91 | return 'code' 92 | 93 | def shortcode(self): 94 | shortcode = '' 95 | 96 | if self.type() == 'image': 97 | shortcode = "" % (self.filename, self.file.url) 98 | 99 | if self.type() == 'code': 100 | shortcode = "[local %s]" % self.file.name 101 | 102 | return shortcode 103 | 104 | 105 | class Setting(models.Model): 106 | """ 107 | Settings model for specific blog settings 108 | """ 109 | blog_title = models.CharField(max_length=75, null=True, blank=True) 110 | blog_tagline = models.CharField(max_length=150, null=True, blank=True) 111 | name = models.CharField(max_length=30, null=True, blank=True) 112 | bio = models.TextField(null=True, blank=True) 113 | disqus_shortname = models.CharField(max_length=75, null=True, blank=True) 114 | user = models.ForeignKey(User, editable=False) 115 | timezone = TimeZoneField(default="US/Central") 116 | 117 | objects = SettingManager() 118 | 119 | 120 | class Profile(models.Model): 121 | git_access_token = models.CharField(max_length=75, null=True) 122 | user = models.OneToOneField(User) 123 | meta = models.TextField(null=True) 124 | 125 | def get_meta(self): 126 | from django.utils import simplejson 127 | if self.meta: 128 | return simplejson.loads(self.meta) 129 | return simplejson.loads('{}') 130 | 131 | 132 | class SystemNotification(models.Model): 133 | """Simple system notifications""" 134 | body = models.TextField(null=False, blank=False) 135 | enabled = models.BooleanField(null=False, default=False) 136 | create_dt = models.DateTimeField(auto_now_add=True) 137 | 138 | def save(self, *args, **kwargs): 139 | super(SystemNotification, self).save(*args, **kwargs) 140 | 141 | # Invalidate cache 142 | cache.delete('system_notifications') 143 | -------------------------------------------------------------------------------- /apps/codrspace/pygments/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codrspace/codrspace/36a409ffe7a06a8844c290235e552ad201a9a58a/apps/codrspace/pygments/__init__.py -------------------------------------------------------------------------------- /apps/codrspace/pygments/styles/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codrspace/codrspace/36a409ffe7a06a8844c290235e552ad201a9a58a/apps/codrspace/pygments/styles/__init__.py -------------------------------------------------------------------------------- /apps/codrspace/pygments/styles/github.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | github 4 | ~~~~~~ 5 | 6 | Port of the github color scheme. 7 | 8 | Based upon the pygments-style-railscasts_ of Marcus Fredriksson. 9 | 10 | .. _pygments-style-railscasts: http://github.com/DrMegahertz/pygments-style-railscasts 11 | 12 | :copyright: Copyright 2012 Hugo Maia Vieira 13 | :license: BSD, see LICENSE for details. 14 | """ 15 | 16 | from pygments.style import Style 17 | from pygments.token import Comment, Error, Generic, Keyword, Literal, Name, \ 18 | Operator, Text 19 | 20 | 21 | class GithubStyle(Style): 22 | """ 23 | Port of the github color scheme. 24 | """ 25 | 26 | default_style = '' 27 | 28 | background_color = '#ffffff' 29 | 30 | styles = { 31 | Comment.Multiline: 'italic #999988', 32 | Comment.Preproc: 'bold #999999', 33 | Comment.Single: 'italic #999988', 34 | Comment.Special: 'bold italic #999999', 35 | Comment: 'italic #999988', 36 | Error: 'bg:#e3d2d2 #a61717', 37 | Generic.Deleted: 'bg:#ffdddd #000000', 38 | Generic.Emph: 'italic #000000', 39 | Generic.Error: '#aa0000', 40 | Generic.Heading: '#999999', 41 | Generic.Inserted: 'bg:#ddffdd #000000', 42 | Generic.Output: '#888888', 43 | Generic.Prompt: '#555555', 44 | Generic.Strong: 'bold', 45 | Generic.Subheading: '#aaaaaa', 46 | Generic.Traceback: '#aa0000', 47 | Keyword.Constant: 'bold #000000 ', 48 | Keyword.Declaration: 'bold #000000', 49 | Keyword.Namespace: 'bold #000000', 50 | Keyword.Pseudo: 'bold #000000', 51 | Keyword.Reserved: 'bold #000000', 52 | Keyword.Type: 'bold #445588', 53 | Keyword: 'bold #000000', 54 | Literal.Number.Float: '#009999', 55 | Literal.Number.Hex: '#009999', 56 | Literal.Number.Integer.Long: '#009999', 57 | Literal.Number.Integer: '#009999', 58 | Literal.Number.Oct: '#009999', 59 | Literal.Number: '#009999', 60 | Literal.String.Backtick: '#d14', 61 | Literal.String.Char: '#d14', 62 | Literal.String.Doc: '#d14', 63 | Literal.String.Double: '#d14', 64 | Literal.String.Escape: '#d14', 65 | Literal.String.Heredoc: '#d14', 66 | Literal.String.Interpol: '#d14', 67 | Literal.String.Other: '#d14', 68 | Literal.String.Regex: '#009926', 69 | Literal.String.Single: '#d14', 70 | Literal.String.Symbol: '#990073', 71 | Literal.String: '#d14', 72 | Name.Attribute: '#008080', 73 | Name.Builtin.Pseudo: '#999999', 74 | Name.Builtin: '#0086B3', 75 | Name.Class: 'bold #445588', 76 | Name.Constant: '#008080', 77 | Name.Decorator: 'bold #3c5d5d', 78 | Name.Entity: '#800080', 79 | Name.Exception: 'bold #990000', 80 | Name.Function: 'bold #990000', 81 | Name.Label: 'bold #990000', 82 | Name.Namespace: '#555555', 83 | Name.Tag: '#000080', 84 | Name.Variable.Class: '#008080', 85 | Name.Variable.Global: '#008080', 86 | Name.Variable.Instance: '#008080', 87 | Name.Variable: '#008080', 88 | Operator.Word: 'bold #000000', 89 | Operator: 'bold #000000', 90 | Text.Whitespace: '#bbbbbb', 91 | } 92 | -------------------------------------------------------------------------------- /apps/codrspace/pygments/styles/solarized.py: -------------------------------------------------------------------------------- 1 | """ 2 | A Pygments_ style based on the dark background variant of Solarized_. 3 | 4 | .. _Pygments: http://pygments.org/ 5 | .. _Solarized: http://ethanschoonover.com/solarized 6 | """ 7 | from pygments.style import Style 8 | from pygments.token import Token, Comment, Name, Keyword, Generic, Number, Operator, String 9 | 10 | BASE03 = '#002B36' 11 | BASE02 = '#073642' 12 | BASE01 = '#586E75' 13 | BASE00 = '#657B83' 14 | BASE0 = '#839496' 15 | BASE1 = '#93A1A1' 16 | BASE2 = '#EEE8D5' 17 | BASE3 = '#FDF6E3' 18 | YELLOW = '#B58900' 19 | ORANGE = '#CB4B16' 20 | RED = '#DC322F' 21 | MAGENTA = '#D33682' 22 | VIOLET = '#6C71C4' 23 | BLUE = '#268BD2' 24 | CYAN = '#2AA198' 25 | GREEN = '#859900' 26 | 27 | class SolarizedStyle(Style): 28 | background_color = BASE03 29 | styles = { 30 | Keyword: GREEN, 31 | Keyword.Constant: ORANGE, 32 | Keyword.Declaration: BLUE, 33 | #Keyword.Namespace 34 | #Keyword.Pseudo 35 | Keyword.Reserved: BLUE, 36 | Keyword.Type: RED, 37 | 38 | #Name 39 | Name.Attribute: BASE1, 40 | Name.Builtin: YELLOW, 41 | Name.Builtin.Pseudo: BLUE, 42 | Name.Class: BLUE, 43 | Name.Constant: ORANGE, 44 | Name.Decorator: BLUE, 45 | Name.Entity: ORANGE, 46 | Name.Exception: ORANGE, 47 | Name.Function: BLUE, 48 | #Name.Label 49 | #Name.Namespace 50 | #Name.Other 51 | Name.Tag: BLUE, 52 | Name.Variable: BLUE, 53 | #Name.Variable.Class 54 | #Name.Variable.Global 55 | #Name.Variable.Instance 56 | 57 | #Literal 58 | #Literal.Date 59 | String: CYAN, 60 | String.Backtick: BASE01, 61 | String.Char: CYAN, 62 | String.Doc: BASE1, 63 | #String.Double 64 | String.Escape: ORANGE, 65 | String.Heredoc: BASE1, 66 | #String.Interpol 67 | #String.Other 68 | String.Regex: RED, 69 | #String.Single 70 | #String.Symbol 71 | Number: CYAN, 72 | #Number.Float 73 | #Number.Hex 74 | #Number.Integer 75 | #Number.Integer.Long 76 | #Number.Oct 77 | 78 | Operator: GREEN, 79 | #Operator.Word 80 | 81 | #Punctuation: ORANGE, 82 | 83 | Comment: BASE01, 84 | #Comment.Multiline 85 | Comment.Preproc: GREEN, 86 | #Comment.Single 87 | Comment.Special: GREEN, 88 | 89 | #Generic 90 | Generic.Deleted: CYAN, 91 | Generic.Emph: 'italic', 92 | Generic.Error: RED, 93 | Generic.Heading: ORANGE, 94 | Generic.Inserted: GREEN, 95 | #Generic.Output 96 | #Generic.Prompt 97 | Generic.Strong: 'bold', 98 | Generic.Subheading: ORANGE, 99 | #Generic.Traceback 100 | 101 | Token: BASE1, 102 | Token.Other: ORANGE, 103 | } 104 | -------------------------------------------------------------------------------- /apps/codrspace/site_maps.py: -------------------------------------------------------------------------------- 1 | from django.contrib.sitemaps import Sitemap 2 | from django.contrib.auth.models import User 3 | from django.conf import settings 4 | from codrspace.models import Post 5 | 6 | 7 | class DefaultMap(Sitemap): 8 | def items(self): 9 | return [ 10 | '/', 11 | '/signin/' 12 | ] 13 | 14 | def location(self, obj): 15 | return obj 16 | 17 | def priority(self, obj): 18 | return '0.7' 19 | 20 | 21 | class UserMap(Sitemap): 22 | def items(self): 23 | return User.objects.all() 24 | 25 | def location(self, obj): 26 | return '/%s/' % obj.username 27 | 28 | def priority(self, obj): 29 | return '0.5' 30 | 31 | 32 | class PostMap(Sitemap): 33 | changefreq = "never" 34 | priority = 0.5 35 | 36 | def items(self): 37 | return Post.objects.filter(status='published').order_by("-publish_dt") 38 | 39 | def lastmod(self, obj): 40 | return obj.update_dt 41 | -------------------------------------------------------------------------------- /apps/codrspace/static/anytimec.css: -------------------------------------------------------------------------------- 1 | /* anytimec.css 4.1112K (anytime.css 4.1112K) 2 | Copyright 2008-2011 Andrew M. Andrews III (www.AMA3.com). Some Rights 3 | Reserved. This work licensed under the Creative Commons Attribution- 4 | Noncommercial-Share Alike 3.0 Unported License except in jurisdicitons 5 | for which the license has been ported by Creative Commons International, 6 | where the work is licensed under the applicable ported license instead. 7 | For a copy of the unported license, visit 8 | http://creativecommons.org/licenses/by-nc-sa/3.0/ 9 | or send a letter to Creative Commons, 171 Second Street, Suite 300, 10 | San Francisco, California, 94105, USA. For ported versions of the 11 | license, visit http://creativecommons.org/international/ 12 | Any+Time is a trademark of Andrew M. Andrews III. */ 13 | .AnyTime-pkr * {border:0;font: inherit;font-size: x-small;font-style:normal;font-weight:normal;list-style-type:none;margin:0;padding:0;white-space: nowrap} 14 | div.AnyTime-win {background-color:#F0F0F1;border:3px solid #C0C0C0;font:normal normal normal xx-small/normal sans-serif;padding-bottom:0.2em;-moz-border-radius:6px;-webkit-border-radius:6px} 15 | .AnyTime-pkr .AnyTime-cloak {background-color:#D7D7D7;opacity:0.7;filter:alpha(opacity=70)} 16 | .AnyTime-pkr .AnyTime-hdr {background-color:#D0D0D1;color:#606062;font-family:Arial,Helvetica,sans-serif;font-size:medium;font-weight:normal;height:1em;margin:0;padding:0 0 0.4em 0;text-align:center;-moz-border-radius:2px;-webkit-border-radius:2px} 17 | .AnyTime-pkr .AnyTime-x-btn {background-color:#FCFCFF;border:1px solid #F99;color:#FF9F9F;cursor:default;float:right;margin:0.3em;text-align:center;width:1.5em;-moz-border-radius:0.4em;-webkit-border-radius:0.4em} 18 | .AnyTime-pkr .AnyTime-btn {background-color:#FCFCFE;border:1px solid #999;color:#606062;cursor:default;float:left;font-family:Arial,Helvetica,sans-serif;height:1.5em;margin-bottom:1px;margin-right:1px;padding-top:0.1em;-moz-border-radius:0.4em;-webkit-border-radius:0.4em} 19 | .AnyTime-pkr .AnyTime-body {padding:0.5em} 20 | .AnyTime-pkr .AnyTime-date {float:left;padding:0 0.5em} 21 | .AnyTime-pkr .AnyTime-lbl {clear:left;color:#606063;font-family:Arial,Helvetica,sans-serif;font-size:100%;font-weight:normal;font-style:normal;height:1.3em;margin:0;padding:0;text-align:center} 22 | .AnyTime-pkr .AnyTime-yrs {height:2.6em;text-align:center;width:18.6em} 23 | .AnyTime-pkr .AnyTime-yrs-past-btn {width:2.7em} 24 | .AnyTime-pkr .AnyTime-yr-prior-btn, .AnyTime-pkr .AnyTime-yr-cur-btn, .AnyTime-pkr .AnyTime-yr-next-btn {width:3.75em} 25 | .AnyTime-pkr .AnyTime-yrs-ahead-btn {width:2.7em} 26 | .AnyTime-pkr .AnyTime-mons {height:4.8em;text-align:center;width:18.8em} 27 | .AnyTime-pkr .AnyTime-mon-btn {width:2.75em} 28 | .AnyTime-pkr .AnyTime-mon7-btn {clear:left} 29 | .AnyTime-pkr .AnyTime-dom-table {background-color:#F0F0F1;border:1px solid #E3E3E4;border-spacing:1px;width:18.6em} 30 | .AnyTime-pkr th.AnyTime-dow {background-color:#C0C0C1;color:white;font-family:Arial,Helvetica,sans-serif;font-size:95%;font-weight:normal;font-style:normal} 31 | .AnyTime-pkr .AnyTime-dom-btn {float:none;height:1.7em;text-align:right;padding:0 0.5em 0 0} 32 | .AnyTime-pkr .AnyTime-dom-btn-empty {background-color:#F3F3F4;border:1px solid #C0C0c1} 33 | .AnyTime-pkr .AnyTime-time {float:left;padding:0 0 0 1em;text-align:center} 34 | .AnyTime-pkr .AnyTime-hrs {float:left;padding-left:0.5em;padding-right:0.5em;text-align:center;width:7.2em} 35 | .AnyTime-pkr .AnyTime-hrs-am, .AnyTime-pkr .AnyTime-hrs-pm {float:left;width:3.6em} 36 | .AnyTime-pkr .AnyTime-hr-btn {text-align:right;padding-right:0.25em;width:3em; } 37 | .AnyTime-pkr .AnyTime-mins {float:left;padding-left:0.5em;padding-right:0.5em;text-align:center;width:4.7em} 38 | .AnyTime-pkr .AnyTime-mins-tens, .AnyTime-pkr .AnyTime-mins-ones {float:left;width:2.3em} 39 | .AnyTime-pkr .AnyTime-min-ten-btn, .AnyTime-pkr .AnyTime-min-one-btn {float:left;text-align:center;width:2em} 40 | .AnyTime-pkr .AnyTime-min-ten-btn-empty, .AnyTime-pkr .AnyTime-min-one-btn-empty {background-color:#F3F3F4;border:1px solid #C0C0c1} 41 | .AnyTime-pkr .AnyTime-secs {float:left;padding-left:0.5em;padding-right:0.5em;text-align:center;width:4.7em} 42 | .AnyTime-pkr .AnyTime-secs-tens, .AnyTime-pkr .AnyTime-secs-ones {float:left;width:2.3em} 43 | .AnyTime-pkr .AnyTime-sec-ten-btn, .AnyTime-pkr .AnyTime-sec-one-btn {float:left;text-align:center;width:2em} 44 | .AnyTime-pkr .AnyTime-sec-ten-btn-empty, .AnyTime-pkr .AnyTime-sec-one-btn-empty {background-color:#F3F3F4;border:1px solid #C0C0c1} 45 | .AnyTime-pkr .AnyTime-offs {clear:left;float:left;padding-left:0.5em;padding-top:0.5em;text-align:center} 46 | .AnyTime-pkr .AnyTime-off-select-btn {width:1.5em} 47 | .AnyTime-pkr .AnyTime-body-yr-selector {padding:1em; } 48 | .AnyTime-pkr .AnyTime-yr-mil, .AnyTime-pkr .AnyTime-yr-cent, .AnyTime-pkr .AnyTime-yr-dec, .AnyTime-pkr .AnyTime-yr-yr {float:left;width:2.5em} 49 | .AnyTime-pkr .AnyTime-mil-btn, .AnyTime-pkr .AnyTime-cent-btn, .AnyTime-pkr .AnyTime-dec-btn, .AnyTime-pkr .AnyTime-yr-btn {float:left;text-align:center;width:2em} 50 | .AnyTime-pkr .AnyTime-yr-era {float:left;padding-left:1em;width:4.1em} 51 | .AnyTime-pkr .AnyTime-era-btn {text-align:center;width:3em} 52 | .AnyTime-pkr .AnyTime-body-off-selector {margin:0.5em; } 53 | .AnyTime-pkr .AnyTime-off-off-btn {clear:left;padding-left:1em;padding-right:1em;text-align:left} 54 | .AnyTime-pkr .AnyTime-cur-btn {border:1px solid #333334;background-color:#C0C0C1;color:#FCFCFE;font-weight:bold} 55 | .AnyTime-pkr .AnyTime-out-btn {background-color:#F0F0F1;border:1px solid #C0C0c1} 56 | .AnyTime-pkr .AnyTime-focus-btn {border:1px dashed black} 57 | -------------------------------------------------------------------------------- /apps/codrspace/static/bootstrap-alert.js: -------------------------------------------------------------------------------- 1 | /* ========================================================== 2 | * bootstrap-alert.js v2.0.2 3 | * http://twitter.github.com/bootstrap/javascript.html#alerts 4 | * ========================================================== 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ========================================================== */ 19 | 20 | 21 | !function( $ ){ 22 | 23 | "use strict" 24 | 25 | /* ALERT CLASS DEFINITION 26 | * ====================== */ 27 | 28 | var dismiss = '[data-dismiss="alert"]' 29 | , Alert = function ( el ) { 30 | $(el).on('click', dismiss, this.close) 31 | } 32 | 33 | Alert.prototype = { 34 | 35 | constructor: Alert 36 | 37 | , close: function ( e ) { 38 | var $this = $(this) 39 | , selector = $this.attr('data-target') 40 | , $parent 41 | 42 | if (!selector) { 43 | selector = $this.attr('href') 44 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 45 | } 46 | 47 | $parent = $(selector) 48 | $parent.trigger('close') 49 | 50 | e && e.preventDefault() 51 | 52 | $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent()) 53 | 54 | $parent 55 | .trigger('close') 56 | .removeClass('in') 57 | 58 | function removeElement() { 59 | $parent 60 | .trigger('closed') 61 | .remove() 62 | } 63 | 64 | $.support.transition && $parent.hasClass('fade') ? 65 | $parent.on($.support.transition.end, removeElement) : 66 | removeElement() 67 | } 68 | 69 | } 70 | 71 | 72 | /* ALERT PLUGIN DEFINITION 73 | * ======================= */ 74 | 75 | $.fn.alert = function ( option ) { 76 | return this.each(function () { 77 | var $this = $(this) 78 | , data = $this.data('alert') 79 | if (!data) $this.data('alert', (data = new Alert(this))) 80 | if (typeof option == 'string') data[option].call($this) 81 | }) 82 | } 83 | 84 | $.fn.alert.Constructor = Alert 85 | 86 | 87 | /* ALERT DATA-API 88 | * ============== */ 89 | 90 | $(function () { 91 | $('body').on('click.alert.data-api', dismiss, Alert.prototype.close) 92 | }) 93 | 94 | }( window.jQuery ); -------------------------------------------------------------------------------- /apps/codrspace/static/bootstrap-dropdown.js: -------------------------------------------------------------------------------- 1 | /* ============================================================ 2 | * bootstrap-dropdown.js v2.0.2 3 | * http://twitter.github.com/bootstrap/javascript.html#dropdowns 4 | * ============================================================ 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ============================================================ */ 19 | 20 | 21 | !function( $ ){ 22 | 23 | "use strict" 24 | 25 | /* DROPDOWN CLASS DEFINITION 26 | * ========================= */ 27 | 28 | var toggle = '[data-toggle="dropdown"]' 29 | , Dropdown = function ( element ) { 30 | var $el = $(element).on('click.dropdown.data-api', this.toggle) 31 | $('html').on('click.dropdown.data-api', function () { 32 | $el.parent().removeClass('open') 33 | }) 34 | } 35 | 36 | Dropdown.prototype = { 37 | 38 | constructor: Dropdown 39 | 40 | , toggle: function ( e ) { 41 | var $this = $(this) 42 | , selector = $this.attr('data-target') 43 | , $parent 44 | , isActive 45 | 46 | if (!selector) { 47 | selector = $this.attr('href') 48 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7 49 | } 50 | 51 | $parent = $(selector) 52 | $parent.length || ($parent = $this.parent()) 53 | 54 | isActive = $parent.hasClass('open') 55 | 56 | clearMenus() 57 | !isActive && $parent.toggleClass('open') 58 | 59 | return false 60 | } 61 | 62 | } 63 | 64 | function clearMenus() { 65 | $(toggle).parent().removeClass('open') 66 | } 67 | 68 | 69 | /* DROPDOWN PLUGIN DEFINITION 70 | * ========================== */ 71 | 72 | $.fn.dropdown = function ( option ) { 73 | return this.each(function () { 74 | var $this = $(this) 75 | , data = $this.data('dropdown') 76 | if (!data) $this.data('dropdown', (data = new Dropdown(this))) 77 | if (typeof option == 'string') data[option].call($this) 78 | }) 79 | } 80 | 81 | $.fn.dropdown.Constructor = Dropdown 82 | 83 | 84 | /* APPLY TO STANDARD DROPDOWN ELEMENTS 85 | * =================================== */ 86 | 87 | $(function () { 88 | $('html').on('click.dropdown.data-api', clearMenus) 89 | $('body').on('click.dropdown.data-api', toggle, Dropdown.prototype.toggle) 90 | }) 91 | 92 | }( window.jQuery ); -------------------------------------------------------------------------------- /apps/codrspace/static/bootstrap-modal.js: -------------------------------------------------------------------------------- 1 | /* ========================================================= 2 | * bootstrap-modal.js v2.0.2 3 | * http://twitter.github.com/bootstrap/javascript.html#modals 4 | * ========================================================= 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * ========================================================= */ 19 | 20 | 21 | !function( $ ){ 22 | 23 | "use strict" 24 | 25 | /* MODAL CLASS DEFINITION 26 | * ====================== */ 27 | 28 | var Modal = function ( content, options ) { 29 | this.options = options 30 | this.$element = $(content) 31 | .delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this)) 32 | } 33 | 34 | Modal.prototype = { 35 | 36 | constructor: Modal 37 | 38 | , toggle: function () { 39 | return this[!this.isShown ? 'show' : 'hide']() 40 | } 41 | 42 | , show: function () { 43 | var that = this 44 | 45 | if (this.isShown) return 46 | 47 | $('body').addClass('modal-open') 48 | 49 | this.isShown = true 50 | this.$element.trigger('show') 51 | 52 | escape.call(this) 53 | backdrop.call(this, function () { 54 | var transition = $.support.transition && that.$element.hasClass('fade') 55 | 56 | !that.$element.parent().length && that.$element.appendTo(document.body) //don't move modals dom position 57 | 58 | that.$element 59 | .show() 60 | 61 | if (transition) { 62 | that.$element[0].offsetWidth // force reflow 63 | } 64 | 65 | that.$element.addClass('in') 66 | 67 | transition ? 68 | that.$element.one($.support.transition.end, function () { that.$element.trigger('shown') }) : 69 | that.$element.trigger('shown') 70 | 71 | }) 72 | } 73 | 74 | , hide: function ( e ) { 75 | e && e.preventDefault() 76 | 77 | if (!this.isShown) return 78 | 79 | var that = this 80 | this.isShown = false 81 | 82 | $('body').removeClass('modal-open') 83 | 84 | escape.call(this) 85 | 86 | this.$element 87 | .trigger('hide') 88 | .removeClass('in') 89 | 90 | $.support.transition && this.$element.hasClass('fade') ? 91 | hideWithTransition.call(this) : 92 | hideModal.call(this) 93 | } 94 | 95 | } 96 | 97 | 98 | /* MODAL PRIVATE METHODS 99 | * ===================== */ 100 | 101 | function hideWithTransition() { 102 | var that = this 103 | , timeout = setTimeout(function () { 104 | that.$element.off($.support.transition.end) 105 | hideModal.call(that) 106 | }, 500) 107 | 108 | this.$element.one($.support.transition.end, function () { 109 | clearTimeout(timeout) 110 | hideModal.call(that) 111 | }) 112 | } 113 | 114 | function hideModal( that ) { 115 | this.$element 116 | .hide() 117 | .trigger('hidden') 118 | 119 | backdrop.call(this) 120 | } 121 | 122 | function backdrop( callback ) { 123 | var that = this 124 | , animate = this.$element.hasClass('fade') ? 'fade' : '' 125 | 126 | if (this.isShown && this.options.backdrop) { 127 | var doAnimate = $.support.transition && animate 128 | 129 | this.$backdrop = $('
') 130 | .appendTo(document.body) 131 | 132 | if (this.options.backdrop != 'static') { 133 | this.$backdrop.click($.proxy(this.hide, this)) 134 | } 135 | 136 | if (doAnimate) this.$backdrop[0].offsetWidth // force reflow 137 | 138 | this.$backdrop.addClass('in') 139 | 140 | doAnimate ? 141 | this.$backdrop.one($.support.transition.end, callback) : 142 | callback() 143 | 144 | } else if (!this.isShown && this.$backdrop) { 145 | this.$backdrop.removeClass('in') 146 | 147 | $.support.transition && this.$element.hasClass('fade')? 148 | this.$backdrop.one($.support.transition.end, $.proxy(removeBackdrop, this)) : 149 | removeBackdrop.call(this) 150 | 151 | } else if (callback) { 152 | callback() 153 | } 154 | } 155 | 156 | function removeBackdrop() { 157 | this.$backdrop.remove() 158 | this.$backdrop = null 159 | } 160 | 161 | function escape() { 162 | var that = this 163 | if (this.isShown && this.options.keyboard) { 164 | $(document).on('keyup.dismiss.modal', function ( e ) { 165 | e.which == 27 && that.hide() 166 | }) 167 | } else if (!this.isShown) { 168 | $(document).off('keyup.dismiss.modal') 169 | } 170 | } 171 | 172 | 173 | /* MODAL PLUGIN DEFINITION 174 | * ======================= */ 175 | 176 | $.fn.modal = function ( option ) { 177 | return this.each(function () { 178 | var $this = $(this) 179 | , data = $this.data('modal') 180 | , options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option) 181 | if (!data) $this.data('modal', (data = new Modal(this, options))) 182 | if (typeof option == 'string') data[option]() 183 | else if (options.show) data.show() 184 | }) 185 | } 186 | 187 | $.fn.modal.defaults = { 188 | backdrop: true 189 | , keyboard: true 190 | , show: true 191 | } 192 | 193 | $.fn.modal.Constructor = Modal 194 | 195 | 196 | /* MODAL DATA-API 197 | * ============== */ 198 | 199 | $(function () { 200 | $('body').on('click.modal.data-api', '[data-toggle="modal"]', function ( e ) { 201 | var $this = $(this), href 202 | , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 203 | , option = $target.data('modal') ? 'toggle' : $.extend({}, $target.data(), $this.data()) 204 | 205 | e.preventDefault() 206 | $target.modal(option) 207 | }) 208 | }) 209 | 210 | }( window.jQuery ); -------------------------------------------------------------------------------- /apps/codrspace/static/bootstrap-popover.js: -------------------------------------------------------------------------------- 1 | /* =========================================================== 2 | * bootstrap-popover.js v2.0.2 3 | * http://twitter.github.com/bootstrap/javascript.html#popovers 4 | * =========================================================== 5 | * Copyright 2012 Twitter, Inc. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * =========================================================== */ 19 | 20 | 21 | !function( $ ) { 22 | 23 | "use strict" 24 | 25 | var Popover = function ( element, options ) { 26 | this.init('popover', element, options) 27 | } 28 | 29 | /* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js 30 | ========================================== */ 31 | 32 | Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, { 33 | 34 | constructor: Popover 35 | 36 | , setContent: function () { 37 | var $tip = this.tip() 38 | , title = this.getTitle() 39 | , content = this.getContent() 40 | 41 | $tip.find('.popover-title')[ $.type(title) == 'object' ? 'append' : 'html' ](title) 42 | $tip.find('.popover-content > *')[ $.type(content) == 'object' ? 'append' : 'html' ](content) 43 | 44 | $tip.removeClass('fade top bottom left right in') 45 | } 46 | 47 | , hasContent: function () { 48 | return this.getTitle() || this.getContent() 49 | } 50 | 51 | , getContent: function () { 52 | var content 53 | , $e = this.$element 54 | , o = this.options 55 | 56 | content = $e.attr('data-content') 57 | || (typeof o.content == 'function' ? o.content.call($e[0]) : o.content) 58 | 59 | content = content.toString().replace(/(^\s*|\s*$)/, "") 60 | 61 | return content 62 | } 63 | 64 | , tip: function() { 65 | if (!this.$tip) { 66 | this.$tip = $(this.options.template) 67 | } 68 | return this.$tip 69 | } 70 | 71 | }) 72 | 73 | 74 | /* POPOVER PLUGIN DEFINITION 75 | * ======================= */ 76 | 77 | $.fn.popover = function ( option ) { 78 | return this.each(function () { 79 | var $this = $(this) 80 | , data = $this.data('popover') 81 | , options = typeof option == 'object' && option 82 | if (!data) $this.data('popover', (data = new Popover(this, options))) 83 | if (typeof option == 'string') data[option]() 84 | }) 85 | } 86 | 87 | $.fn.popover.Constructor = Popover 88 | 89 | $.fn.popover.defaults = $.extend({} , $.fn.tooltip.defaults, { 90 | placement: 'right' 91 | , content: '' 92 | , template: 'We couldn't find what you were looking for :(
22 |You could try:
23 |We have been notified and are working hard to fix it!
18 |Thank you for your patience.
19 |{{ post.content|explosivo }}
26 | {% else %} 27 | {% cache 3600 content post.pk %} 28 |{{ post.content|explosivo }}
29 | {% endcache %} 30 | {% endif %} 31 |[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 |{% 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 |{{ api_key.key }}
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.
There are three parameters that will be required when accessing the API:
18 |19 |
format
- currently only json
is supportedusername
- your username {{ user.username }}
api_key
- your api key {{ api_key.key }}
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 }}
curl -X GET {{ SITE_URL }}/api/v1/post/?format=json&username={{ user.username }}&api_key={{ api_key.key }}
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 |
curl -X GET {{ SITE_URL }}/api/v1/post/[id]/?format=json&username={{ user.username }}&api_key={{ api_key.key }}
[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 |
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
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 |
113 |
122 |
publish_dt
.publish_dt
is in UTC (GMT)
126 | 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 }}
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 |
149 |
158 |
publish_dt
.publish_dt
is in UTC (GMT)
162 | 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 }}
[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 |
curl -X DELETE {{ SITE_URL }}/api/v1/post/[id]/?username={{ user.username }}&api_key={{ api_key.key }}
[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 |
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 |{{ post.content|striptags|truncatewords:"100"}}
28 |You are about to delete this post. All good?
32 | 38 |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 |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
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 |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/preview.html: -------------------------------------------------------------------------------- 1 | {% load codrspace_tags %} 2 | {% load short_codes %} 3 | 4 | {% block content %} 5 |{{ post.content|explosivo }}
8 |[^```]+)+?```", 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/templatetags/syntax_color.py:
--------------------------------------------------------------------------------
1 | from django import template
2 | from django.template.defaultfilters import stringfilter
3 | from django.utils.safestring import mark_safe
4 | from pygments import highlight
5 | from pygments.formatters import HtmlFormatter
6 | from pygments.lexers import get_lexer_by_name, \
7 | get_lexer_for_filename, \
8 | guess_lexer, \
9 | ClassNotFound, \
10 | get_all_lexers
11 | from codrspace.pygments.styles.github import GithubStyle
12 |
13 | register = template.Library()
14 |
15 |
16 | def get_lexer_list():
17 | lexers = []
18 |
19 | for lexer in get_all_lexers():
20 | name, aliases, filetypes, mimetypes = lexer
21 | lexers.extend([alias for alias in aliases])
22 |
23 | return lexers
24 |
25 | LEXERS = get_lexer_list()
26 |
27 |
28 | def _colorize_table(value, lang=None):
29 | return mark_safe(highlight(value, get_lexer(value, lang), HtmlFormatter(style=GithubStyle)))
30 |
31 |
32 | def generate_pygments_css(path=None):
33 | if path is None:
34 | import os
35 | path = os.path.join(os.getcwd(), 'pygments.css')
36 | f = open(path, 'w')
37 | f.write(HtmlFormatter(style=GithubStyle).get_style_defs('.highlight'))
38 | f.close()
39 |
40 |
41 | def get_lexer(value, lang):
42 | if lang:
43 | if '.' in lang:
44 | return get_lexer_for_filename(lang) # possibly a filename, poor detection for now
45 | elif lang in LEXERS:
46 | return get_lexer_by_name(lang) # guess it by specific language
47 | # try and guess the lexer by content
48 | return guess_lexer(value)
49 |
50 |
51 | @register.filter(name='colorize')
52 | @stringfilter
53 | def colorize(value, arg=None):
54 | try:
55 | return mark_safe(highlight(value, get_lexer(value, arg), HtmlFormatter(style=GithubStyle)))
56 | except ClassNotFound:
57 | return value
58 |
59 |
60 | @register.filter(name='colorize_table')
61 | @stringfilter
62 | def colorize_table(value, arg=None):
63 | try:
64 | return _colorize_table(value, arg)
65 | except ClassNotFound:
66 | return value
67 |
--------------------------------------------------------------------------------
/apps/codrspace/tests.py:
--------------------------------------------------------------------------------
1 | """
2 | This file demonstrates writing tests using the unittest module. These will pass
3 | when you run "manage.py test".
4 |
5 | Replace this with more appropriate tests for your application.
6 | """
7 |
8 | from django.test import TestCase
9 |
10 |
11 | class SimpleTest(TestCase):
12 | def test_basic_addition(self):
13 | """
14 | Tests that 1 + 1 always equals 2.
15 | """
16 | self.assertEqual(1 + 1, 2)
17 |
--------------------------------------------------------------------------------
/apps/codrspace/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls.defaults import patterns, url, include
2 | from django.views.decorators.cache import cache_page
3 | from django.contrib.sitemaps import views as sitemaps_views
4 | from django.http import HttpResponse
5 |
6 | from tastypie.api import Api
7 |
8 | from codrspace.feeds import LatestPostsFeed
9 | from codrspace.api import PostResource
10 | from codrspace.site_maps import DefaultMap, PostMap, UserMap
11 |
12 | site_maps = {
13 | 'default': DefaultMap,
14 | 'posts': PostMap,
15 | 'users': UserMap
16 | }
17 |
18 | api = Api(api_name='v1')
19 | api.register(PostResource())
20 |
21 |
22 | urlpatterns = patterns('codrspace.views',
23 | url(r'^$', 'index', name="homepage"),
24 |
25 | url(r'^admin/add/$', 'add', name="add"),
26 | url(r'^admin/edit/(?P\d+)/$', 'edit', name="edit"),
27 | url(r'^admin/delete/(?P\d+)/$', 'delete', name="delete"),
28 | url(r'^admin/drafts/$', 'drafts', name="drafts"),
29 | url(r'^admin/preview/$', 'render_preview', name="render_preview"),
30 |
31 | url(r'^settings/$', 'user_settings', name="user_settings"),
32 | url(r'^api-settings/', 'api_settings', name="api_settings"),
33 |
34 | url(r'^signin/$', 'signin_start', name="signin_start"),
35 | url(r'^signin_callback/$', 'signin_callback', name="signin_callback"),
36 | url(r'^signout/$', 'signout', name="signout"),
37 |
38 | url(r'^donate/$', 'donate', name="donate"),
39 | url(r'^feedback/$', 'feedback', name="feedback"),
40 | url(r'^help/$', 'help', name="help"),
41 | url(r'^api/', include(api.urls)),
42 | )
43 |
44 | urlpatterns += patterns('codrspace.mock_views',
45 | url(r'^fake_user/$', 'fake_user', name="fake_user"),
46 | url(r'^authorize/$', 'authorize', name="authorize"),
47 | url(r'^access_token/$', 'access_token', name="access_token"),
48 | )
49 |
50 | urlpatterns += patterns('codrspace.views',
51 | url(r'^download/(?P[\w\d\-\.]+)/$', 'posts_download', name="posts_download"),
52 | url(r'^(?P[\w\d\-\.]+)/feed/$', LatestPostsFeed(), name="posts_feed"),
53 | url(r'^(?P[\w\d\-\.]+)/(?P[\w\d\-]+)/$', 'post_detail',
54 | name="post_detail"),
55 | url(r'^(?P[\w\d\-\.]+)/$', 'post_list', name="post_list")
56 | )
57 |
58 | urlpatterns += patterns('',
59 | (r'^robots\.txt$', lambda r: HttpResponse("User-agent: *\nCrawl-delay: 5", mimetype="text/plain")),
60 | (r'^sitemap\.xml$', cache_page(86400)(sitemaps_views.sitemap), {'sitemaps': site_maps})
61 | )
62 |
--------------------------------------------------------------------------------
/apps/codrspace/utils.py:
--------------------------------------------------------------------------------
1 | from timezones.utils import adjust_datetime_to_timezone
2 | from bs4 import BeautifulSoup, Comment
3 |
4 | from django.conf import settings
5 |
6 |
7 | def localize_date(date, from_tz=None, to_tz=None):
8 | """
9 | Convert from one timezone to another
10 | """
11 | # set the defaults
12 | if from_tz is None:
13 | from_tz = settings.TIME_ZONE
14 |
15 | if to_tz is None:
16 | to_tz = "US/Central"
17 |
18 | date = adjust_datetime_to_timezone(date, from_tz=from_tz, to_tz=to_tz)
19 | date = date.replace(tzinfo=None)
20 | return date
21 |
22 |
23 | def clean_html(html):
24 | """Use beautifulsoup4 to clean html"""
25 | allowed_tags = ['p', 'br', 'span', 'small', 'strike']
26 | allowed_attributes = []
27 |
28 | soup = BeautifulSoup(html, "html.parser")
29 |
30 | # strip unwanted tags an attribute
31 | for tag in soup.findAll():
32 | if tag.name.lower() not in allowed_tags:
33 | tag.extract()
34 | continue
35 |
36 | for attr in tag.attrs.keys():
37 | if attr not in allowed_attributes:
38 | del tag.attrs[attr]
39 |
40 | # scripts can be executed from comments in some cases
41 | comments = soup.findAll(text=lambda text: isinstance(text, Comment))
42 | for comment in comments:
43 | comment.extract()
44 |
45 | return unicode(soup)
46 |
47 |
48 | def apply_class(html, element, _class):
49 | """Apply a class to a all elements of type element"""
50 | soup = BeautifulSoup(html, "html.parser")
51 |
52 | # strip unwanted tags an attribute
53 | for tag in soup.findAll(element):
54 | tag.attrs['class'] = _class
55 |
56 | return unicode(soup)
57 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/deploy/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Codrspace/codrspace/36a409ffe7a06a8844c290235e552ad201a9a58a/deploy/__init__.py
--------------------------------------------------------------------------------
/deploy/wsgi.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 |
4 | # redirect sys.stdout to sys.stderr for bad libraries like geopy that uses
5 | # print statements for optional import exceptions.
6 | sys.stdout = sys.stderr
7 |
8 | from os.path import abspath, dirname, join
9 | from site import addsitedir
10 |
11 | sys.path.insert(0, abspath(join(dirname(__file__), "../")))
12 |
13 | os.environ["DJANGO_SETTINGS_MODULE"] = "settings"
14 |
15 | from django.conf import settings
16 | sys.path.insert(0, join(settings.PROJECT_ROOT))
17 |
18 | from django.core.handlers.wsgi import WSGIHandler
19 | application = WSGIHandler()
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.1'
2 |
3 | services:
4 | database:
5 | image: mysql
6 | restart: always
7 | environment:
8 | MYSQL_ROOT_PASSWORD: "development"
9 | MYSQL_DATABASE: "codrspace"
10 | MYSQL_USER: "codrspace"
11 | MYSQL_PASSWORD: "development"
12 |
13 | codrspace:
14 | build: "."
15 | command: bash -c "sleep 10 && python manage.py syncdb && python manage.py runserver 0.0.0.0:9000"
16 | depends_on:
17 | - "database"
18 | ports:
19 | - "9000:9000"
20 | volumes:
21 | - ".:/code/codrspace_app"
22 |
--------------------------------------------------------------------------------
/example_local_settings.py:
--------------------------------------------------------------------------------
1 | """
2 | The following settings are meant to be used for a faked github.com OAuth
3 | instance. You should rename this file to 'local_settings.py' in order to use
4 | it for the instance it resides in.
5 |
6 | The are 2 main reasons for using local_settings.py when running locally:
7 | 1. Disable CSRF protection (just not necessary when running on localhost)
8 | 2. Disable file-based caching (who cares if things are slower when coding!)
9 | - Feel free to run with caching enabled locally, but it can cause
10 | headaches when developing because models, etc. are always changing so
11 | it doesn't serve a lot of purpose.
12 | """
13 |
14 |
15 | from frappy.services.github import Github
16 |
17 | def get_setting(setting):
18 | """
19 | Get a setting from settings.py
20 | """
21 | import settings
22 | return getattr(settings, setting)
23 |
24 | # Development debug mode
25 | DEBUG = True
26 | TEMPLATE_DEBUG = DEBUG
27 |
28 | # Replace with your own github credentials to run locally and have your profile
29 | # filled out automatically without an access token from the fake Oauth instance
30 | GITHUB_AUTH = {
31 | 'client_id': 'doesntmatter',
32 | 'secret': 'doesntmatter',
33 | 'callback_url': 'http://127.0.0.1:8000/signin_callback',
34 | 'auth_url': 'http://127.0.0.1:9000/authorize/',
35 | 'access_token_url': 'http://127.0.0.1:9000/access_token/',
36 |
37 | # Get information of authenticated user
38 | 'user_url': 'http://127.0.0.1:9000/fake_user/',
39 |
40 | # User that is 'logged in' by fake api setup
41 | 'username': 'octocat',
42 |
43 | # Debug mode - for faking auth_url
44 | 'debug': True
45 | }
46 |
47 | # set this to the JSON that comes back from http://api.github.com/user/
48 | user = Github().users(GITHUB_AUTH['username'])
49 | GITHUB_USER_JSON = user.response_json
50 |
51 |
52 | # Overrides for middleware to avoid having the CSRF protection when running
53 | # locally
54 | MIDDLEWARE_CLASSES = get_setting('MIDDLEWARE_CLASSES')
55 | MIDDLEWARE_CLASSES = (
56 | 'django.middleware.common.CommonMiddleware',
57 | 'django.contrib.sessions.middleware.SessionMiddleware',
58 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
59 | 'django.contrib.messages.middleware.MessageMiddleware',
60 | )
61 |
62 | # Disable caching
63 | CACHES = get_setting('CACHES')
64 | CACHES = {
65 | 'default': {
66 | 'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from django.core.management import execute_manager
3 | import imp
4 | try:
5 | imp.find_module('settings') # Assumed to be in the same directory.
6 | except ImportError:
7 | import sys
8 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__)
9 | sys.exit(1)
10 |
11 | import settings
12 |
13 | if __name__ == "__main__":
14 | execute_manager(settings)
15 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/requirements_dev.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 | django-debug-toolbar
11 | MySQL-python==1.2.5
12 | requests
13 | -e git://github.com/durden/frappy.git#egg=frappy
14 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf import settings
2 | from django.conf.urls.static import static
3 | from django.conf.urls.defaults import patterns, include, url
4 |
5 | # Uncomment the next two lines to enable the admin:
6 | # from django.contrib import admin
7 | # admin.autodiscover()
8 |
9 | urlpatterns = patterns('',
10 | # Examples:
11 | # url(r'^', 'codrspace.views.home'),
12 | url(r'^', include('codrspace.urls')),
13 |
14 | # Uncomment the admin/doc line below to enable admin documentation:
15 | # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
16 |
17 | # Uncomment the next line to enable the admin:
18 | # url(r'^admin/', include(admin.site.urls)),
19 | ) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
20 |
--------------------------------------------------------------------------------