Merge branch 'auth'

Conflicts:
	readthedocs/builds/filters.py
	readthedocs/templates/builds/build_list.html
rtd2
Eric Holscher 2012-10-15 16:39:23 -07:00
commit 670ffe4ad4
28 changed files with 1226 additions and 234 deletions

View File

@ -28,6 +28,7 @@ Site Documentation
faq
support
features
privacy
webhooks
alternate_domains
sponsors

66
docs/privacy.rst Normal file
View File

@ -0,0 +1,66 @@
Privacy Levels
==============
Read the Docs supports 3 different privacy levels on 2 different objects;
Public, Protected, Private on Projects and Versions.
Understanding the Privacy Levels
--------------------------------
+------------+------------+-----------+
| Level | Detail | Listing |
+============+============+===========+
| Private | No | No |
+------------+------------+-----------+
| Protected | Yes | No |
+------------+------------+-----------+
| Public | Yes | Yes |
+------------+------------+-----------+
Public
~~~~~~
This is the easiest and most obvious. It is also the default. It means that everything is available to be seen by everyone.
Protected
~~~~~~~~~
Protected means that your object won't show up in Listing Pages, but Detail pages still still work.
For example, a Project that is Protected will not show on the homepage Recently Updated list,
however, if you link directly to the project, you will get a 200 and the page will display.
Protected Versions are similar, they won't show up in your version listings, but will be available once linked to.
Private
~~~~~~~
Private objects are available only to people who have permissions so see them.
They will not display on any list view, and will 404 when you link them to others.
Project Objects
----------------
Detail Views
~~~~~~~~~~~~
* Project Detail (/projects/<slug>)
* API Detail (/api/v1/project/<slug>/)
List Views
~~~~~~~~~~
* Home Page
* All Projects Page
* User Profile Page (/profiles/<user>/)
Version Objects
----------------
List Views
~~~~~~~~~~
* Project Detail (/projects/<slug>)
* Version Selector on Home page
* Version Selector on Documentation page

View File

@ -1,3 +1,4 @@
# Pypi ftw.
Unipath==0.2.1
bzr==2.5b4
celery==3.0.9
@ -19,6 +20,8 @@ slumber==0.4.2
Sphinx==1.1.2
unittest-xml-reporting==1.3.1
sphinx-http-domain==0.2
django-guardian==1.0.4
# Pegged git requirements
git+https://github.com/toastdriven/django-haystack@259274e4127f723d76b893c87a82777f9490b960#egg=django_haystack
hg+http://bitbucket.org/andrewgodwin/south/@ecaafda23e600e510e252734d67bf8f9f2362dc9#egg=South-tip
@ -28,3 +31,4 @@ git+http://github.com/ericflo/django-pagination.git@47e7ec874cd7dddda5ed13ffb699
git+http://github.com/nathanborror/django-basic-apps.git@171fdbe21a0dbbb38919a383cc265cb3cbc73771#egg=django_basic_apps-dev
git+http://github.com/nathanborror/django-registration.git@dc0b564b7bfb79f58592fe8ad836729a85ec17ae#egg=django_registration-dev
git+https://github.com/toastdriven/django-tastypie.git@c5451b90b18b0cb64841b2276d543230d5f58231#egg=django_tastypie-dev

View File

@ -29,183 +29,15 @@ from djangome import views as djangome
log = logging.getLogger(__name__)
class SearchMixin(object):
'''
Adds a search api to any ModelResource provided the model is indexed.
The search can be configured using the Meta class in each ModelResource.
The search is limited to the model defined by the meta queryset. If the
search is invalid, a 400 Bad Request will be raised.
e.g.
class Meta:
# Return facet counts for each facetname
search_facets = ['facetname1', 'facetname1']
# Number of results returned per page
search_page_size = 20
# Highlight search terms in the text
search_highlight = True
'''
def get_search(self, request, **kwargs):
self.method_check(request, allowed=['get'])
self.is_authenticated(request)
self.throttle_check(request)
object_list = self._search(request,
self._meta.queryset.model,
facets = getattr(self._meta, 'search_facets', []),
page_size = getattr(self._meta, 'search_page_size', 20),
highlight = getattr(self._meta, 'search_highlight', True),
)
self.log_throttled_access(request)
return self.create_response(request, object_list)
def _url_template(self, query, selected_facets):
'''
Construct a url template to assist with navigating the resources.
This looks a bit nasty but urllib.urlencode resulted in even
nastier output...
'''
query_params = []
for facet in selected_facets:
query_params.append(('selected_facets', facet))
query_params += [('q', query), ('format', 'json'), ('page', '{0}')]
query_string = '&'.join('='.join(p) for p in query_params)
url_template = reverse('api_get_search', kwargs={
'resource_name': self._meta.resource_name,
'api_name': 'v1'
})
return url_template + '?' + query_string
def _search(self, request, model, facets=None, page_size=20, highlight=True):
'''
`facets`
A list of facets to include with the results
`models`
Limit the search to one or more models
'''
form = FacetedSearchForm(request.GET, facets=facets or [],
models=(model,), load_all=True)
if not form.is_valid():
return self.error_response({'errors': form.errors }, request)
results = form.search()
paginator = Paginator(results, page_size)
try:
page = paginator.page(int(request.GET.get('page', 1)))
except InvalidPage:
raise Http404(ugettext("Sorry, no results on that page."))
objects = []
query = request.GET.get('q', '')
highlighter = Highlighter(query)
for result in page.object_list:
if not result:
continue
text = result.text
if highlight:
text = highlighter.highlight(text)
bundle = self.build_bundle(obj=result.object, request=request)
bundle = self.full_dehydrate(bundle)
bundle.data['text'] = text
objects.append(bundle)
url_template = self._url_template(query, form['selected_facets'].value())
page_data = {
'number': page.number,
'per_page': paginator.per_page,
'num_pages': paginator.num_pages,
'page_range': paginator.page_range,
'object_count': paginator.count,
'url_template': url_template,
}
if page.has_next():
page_data['url_next'] = url_template.format(page.next_page_number())
if page.has_previous():
page_data['url_prev'] = url_template.format(page.previous_page_number())
object_list = {
'page': page_data,
'objects': objects,
}
if facets:
object_list.update({'facets': results.facet_counts()})
return object_list
# XXX: This method is available in the latest tastypie, remove
# once available in production.
def error_response(self, errors, request):
if request:
desired_format = self.determine_format(request)
else:
desired_format = self._meta.default_format
serialized = self.serialize(request, errors, desired_format)
response = http.HttpBadRequest(content=serialized, content_type=build_content_type(desired_format))
raise ImmediateHttpResponse(response=response)
class PostAuthentication(BasicAuthentication):
def is_authenticated(self, request, **kwargs):
if request.method == "GET":
return True
return super(PostAuthentication, self).is_authenticated(request, **kwargs)
class EnhancedModelResource(ModelResource):
def obj_get_list(self, request=None, **kwargs):
"""
A ORM-specific implementation of ``obj_get_list``.
Takes an optional ``request`` object, whose ``GET`` dictionary can be
used to narrow the query.
"""
filters = None
if hasattr(request, 'GET'):
filters = request.GET
applicable_filters = self.build_filters(filters=filters)
applicable_filters.update(kwargs)
try:
return self.get_object_list(request).filter(**applicable_filters)
except ValueError, e:
raise NotFound(ugettext("Invalid resource lookup data provided (mismatched type).: %(error)s") % {'error': e})
class UserResource(ModelResource):
class Meta:
allowed_methods = ['get']
queryset = User.objects.all()
fields = ['username', 'first_name', 'last_name', 'last_login', 'id']
filtering = {
'username': 'exact',
}
def override_urls(self):
return [
url(r"^(?P<resource_name>%s)/schema/$" % self._meta.resource_name, self.wrap_view('get_schema'), name="api_get_schema"),
url(r"^(?P<resource_name>%s)/(?P<username>[a-z-_]+)/$" % self._meta.resource_name, self.wrap_view('dispatch_detail'), name="api_dispatch_detail"),
]
class OwnerAuthorization(Authorization):
def apply_limits(self, request, object_list):
if request and hasattr(request, 'user') and request.method != 'GET':
if request.user.is_authenticated():
object_list = object_list.filter(users__in=[request.user])
else:
object_list = object_list.none()
return object_list
from .utils import SearchMixin, PostAuthentication, EnhancedModelResource
class ProjectResource(ModelResource, SearchMixin):
users = fields.ToManyField(UserResource, 'users')
users = fields.ToManyField('api.base.UserResource', 'users')
class Meta:
include_absolute_url = True
allowed_methods = ['get', 'post', 'put']
queryset = Project.objects.all()
queryset = Project.objects.public()
authentication = PostAuthentication()
authorization = DjangoAuthorization()
excludes = ['path', 'featured']
@ -214,6 +46,10 @@ class ProjectResource(ModelResource, SearchMixin):
"slug": ALL_WITH_RELATIONS,
}
def get_object_list(self, request):
self._meta.queryset = Project.objects.public(user=request.user)
return super(ProjectResource, self).get_object_list(request)
def dehydrate(self, bundle):
bundle.data['subdomain'] = "http://%s/" % bundle.obj.subdomain
return bundle
@ -244,29 +80,6 @@ class ProjectResource(ModelResource, SearchMixin):
]
class BuildResource(EnhancedModelResource):
project = fields.ForeignKey(ProjectResource, 'project')
version = fields.ForeignKey('api.base.VersionResource', 'version')
class Meta:
include_absolute_url = True
allowed_methods = ['get', 'post', 'put']
queryset = Build.objects.all()
authentication = PostAuthentication()
authorization = DjangoAuthorization()
filtering = {
"project": ALL_WITH_RELATIONS,
"slug": ALL_WITH_RELATIONS,
"type": ALL_WITH_RELATIONS,
"state": ALL_WITH_RELATIONS,
}
def override_urls(self):
return [
url(r"^(?P<resource_name>%s)/schema/$" % self._meta.resource_name, self.wrap_view('get_schema'), name="api_get_schema"),
url(r"^(?P<resource_name>%s)/(?P<project__slug>[a-z-_]+)/$" % self._meta.resource_name, self.wrap_view('dispatch_list'), name="build_list_detail"),
]
class VersionResource(EnhancedModelResource):
project = fields.ForeignKey(ProjectResource, 'project', full=True)
@ -274,7 +87,7 @@ class VersionResource(EnhancedModelResource):
queryset = Version.objects.all()
allowed_methods = ['get', 'put', 'post']
always_return_data = True
queryset = Version.objects.all()
queryset = Version.objects.public()
authentication = PostAuthentication()
authorization = DjangoAuthorization()
filtering = {
@ -288,6 +101,10 @@ class VersionResource(EnhancedModelResource):
#bundle.data['subdomain'] = "http://%s/en/%s/" % (bundle.obj.project.subdomain, bundle.obj.slug)
#return bundle
def get_object_list(self, request):
self._meta.queryset = Version.objects.public(user=request.user)
return super(VersionResource, self).get_object_list(request)
def version_compare(self, request, **kwargs):
project = get_object_or_404(Project, slug=kwargs['project_slug'])
highest = highest_version(project.versions.filter(active=True))
@ -331,6 +148,28 @@ class VersionResource(EnhancedModelResource):
url(r"^(?P<resource_name>%s)/(?P<project_slug>[a-z-_]+)/(?P<version_slug>[a-z0-9-_.]+)/build/$" % self._meta.resource_name, self.wrap_view('build_version'), name="api_version_build_slug"),
]
class BuildResource(EnhancedModelResource):
project = fields.ForeignKey(ProjectResource, 'project')
version = fields.ForeignKey('api.base.VersionResource', 'version')
class Meta:
include_absolute_url = True
allowed_methods = ['get', 'post', 'put']
queryset = Build.objects.all()
authentication = PostAuthentication()
authorization = DjangoAuthorization()
filtering = {
"project": ALL_WITH_RELATIONS,
"slug": ALL_WITH_RELATIONS,
"type": ALL_WITH_RELATIONS,
"state": ALL_WITH_RELATIONS,
}
def override_urls(self):
return [
url(r"^(?P<resource_name>%s)/schema/$" % self._meta.resource_name, self.wrap_view('get_schema'), name="api_get_schema"),
url(r"^(?P<resource_name>%s)/(?P<project__slug>[a-z-_]+)/$" % self._meta.resource_name, self.wrap_view('dispatch_list'), name="build_list_detail"),
]
class FileResource(EnhancedModelResource, SearchMixin):
project = fields.ForeignKey(ProjectResource, 'project', full=True)
@ -376,3 +215,19 @@ class FileResource(EnhancedModelResource, SearchMixin):
self.log_throttled_access(request)
return self.create_response(request, object_list)
class UserResource(ModelResource):
class Meta:
allowed_methods = ['get']
queryset = User.objects.all()
fields = ['username', 'first_name', 'last_name', 'last_login', 'id']
filtering = {
'username': 'exact',
}
def override_urls(self):
return [
url(r"^(?P<resource_name>%s)/schema/$" % self._meta.resource_name, self.wrap_view('get_schema'), name="api_get_schema"),
url(r"^(?P<resource_name>%s)/(?P<username>[a-z-_]+)/$" % self._meta.resource_name, self.wrap_view('dispatch_detail'), name="api_dispatch_detail"),
]

185
readthedocs/api/utils.py Normal file
View File

@ -0,0 +1,185 @@
import logging
from django.core.paginator import Paginator, InvalidPage
from django.contrib.auth.models import User
from django.conf.urls.defaults import url
from django.shortcuts import get_object_or_404
from django.http import Http404
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext
from haystack.utils import Highlighter
from tastypie import fields
from tastypie.authentication import BasicAuthentication
from tastypie.authorization import Authorization, DjangoAuthorization
from tastypie.constants import ALL_WITH_RELATIONS, ALL
from tastypie.resources import ModelResource
from tastypie.exceptions import NotFound, ImmediateHttpResponse
from tastypie import http
from tastypie.utils.mime import build_content_type
from tastypie.http import HttpCreated
from tastypie.utils import dict_strip_unicode_keys, trailing_slash
from core.forms import FacetedSearchForm
from builds.models import Build, Version
from projects.models import Project, ImportedFile
from projects.utils import highest_version, mkversion
from projects import tasks
from djangome import views as djangome
log = logging.getLogger(__name__)
class SearchMixin(object):
'''
Adds a search api to any ModelResource provided the model is indexed.
The search can be configured using the Meta class in each ModelResource.
The search is limited to the model defined by the meta queryset. If the
search is invalid, a 400 Bad Request will be raised.
e.g.
class Meta:
# Return facet counts for each facetname
search_facets = ['facetname1', 'facetname1']
# Number of results returned per page
search_page_size = 20
# Highlight search terms in the text
search_highlight = True
'''
def get_search(self, request, **kwargs):
self.method_check(request, allowed=['get'])
self.is_authenticated(request)
self.throttle_check(request)
object_list = self._search(request,
self._meta.queryset.model,
facets = getattr(self._meta, 'search_facets', []),
page_size = getattr(self._meta, 'search_page_size', 20),
highlight = getattr(self._meta, 'search_highlight', True),
)
self.log_throttled_access(request)
return self.create_response(request, object_list)
def _url_template(self, query, selected_facets):
'''
Construct a url template to assist with navigating the resources.
This looks a bit nasty but urllib.urlencode resulted in even
nastier output...
'''
query_params = []
for facet in selected_facets:
query_params.append(('selected_facets', facet))
query_params += [('q', query), ('format', 'json'), ('page', '{0}')]
query_string = '&'.join('='.join(p) for p in query_params)
url_template = reverse('api_get_search', kwargs={
'resource_name': self._meta.resource_name,
'api_name': 'v1'
})
return url_template + '?' + query_string
def _search(self, request, model, facets=None, page_size=20, highlight=True):
'''
`facets`
A list of facets to include with the results
`models`
Limit the search to one or more models
'''
form = FacetedSearchForm(request.GET, facets=facets or [],
models=(model,), load_all=True)
if not form.is_valid():
return self.error_response({'errors': form.errors }, request)
results = form.search()
paginator = Paginator(results, page_size)
try:
page = paginator.page(int(request.GET.get('page', 1)))
except InvalidPage:
raise Http404(ugettext("Sorry, no results on that page."))
objects = []
query = request.GET.get('q', '')
highlighter = Highlighter(query)
for result in page.object_list:
if not result:
continue
text = result.text
if highlight:
text = highlighter.highlight(text)
bundle = self.build_bundle(obj=result.object, request=request)
bundle = self.full_dehydrate(bundle)
bundle.data['text'] = text
objects.append(bundle)
url_template = self._url_template(query, form['selected_facets'].value())
page_data = {
'number': page.number,
'per_page': paginator.per_page,
'num_pages': paginator.num_pages,
'page_range': paginator.page_range,
'object_count': paginator.count,
'url_template': url_template,
}
if page.has_next():
page_data['url_next'] = url_template.format(page.next_page_number())
if page.has_previous():
page_data['url_prev'] = url_template.format(page.previous_page_number())
object_list = {
'page': page_data,
'objects': objects,
}
if facets:
object_list.update({'facets': results.facet_counts()})
return object_list
# XXX: This method is available in the latest tastypie, remove
# once available in production.
def error_response(self, errors, request):
if request:
desired_format = self.determine_format(request)
else:
desired_format = self._meta.default_format
serialized = self.serialize(request, errors, desired_format)
response = http.HttpBadRequest(content=serialized, content_type=build_content_type(desired_format))
raise ImmediateHttpResponse(response=response)
class PostAuthentication(BasicAuthentication):
def is_authenticated(self, request, **kwargs):
if request.method == "GET":
return True
return super(PostAuthentication, self).is_authenticated(request, **kwargs)
class EnhancedModelResource(ModelResource):
def obj_get_list(self, request=None, **kwargs):
"""
A ORM-specific implementation of ``obj_get_list``.
Takes an optional ``request`` object, whose ``GET`` dictionary can be
used to narrow the query.
"""
filters = None
if hasattr(request, 'GET'):
filters = request.GET
applicable_filters = self.build_filters(filters=filters)
applicable_filters.update(kwargs)
try:
return self.get_object_list(request).filter(**applicable_filters)
except ValueError, e:
raise NotFound(ugettext("Invalid resource lookup data provided (mismatched type).: %(error)s") % {'error': e})
class OwnerAuthorization(Authorization):
def apply_limits(self, request, object_list):
if request and hasattr(request, 'user') and request.method != 'GET':
if request.user.is_authenticated():
object_list = object_list.filter(users__in=[request.user])
else:
object_list = object_list.none()
return object_list

View File

@ -3,10 +3,15 @@
from django.contrib import admin
from builds.models import Build, VersionAlias, Version
from guardian.admin import GuardedModelAdmin
class BuildAdmin(admin.ModelAdmin):
list_display = ('project', 'date', 'success', 'type', 'state')
class VersionAdmin(GuardedModelAdmin):
search_fields = ('slug', 'project__name')
list_filter = ('project',)
admin.site.register(Build, BuildAdmin)
admin.site.register(VersionAlias)
admin.site.register(Version)
admin.site.register(Version, VersionAdmin)

View File

@ -0,0 +1,145 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'Version.privacy_level'
db.add_column('builds_version', 'privacy_level',
self.gf('django.db.models.fields.CharField')(default='public', max_length=20),
keep_default=False)
def backwards(self, orm):
# Deleting field 'Version.privacy_level'
db.delete_column('builds_version', 'privacy_level')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 10, 13, 23, 55, 6, 898344)'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 10, 13, 23, 55, 6, 898075)'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'builds.build': {
'Meta': {'ordering': "['-date']", 'object_name': 'Build'},
'date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'error': ('django.db.models.fields.TextField', [], {}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'output': ('django.db.models.fields.TextField', [], {}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'builds'", 'to': "orm['projects.Project']"}),
'setup': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'setup_error': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'state': ('django.db.models.fields.CharField', [], {'default': "'finished'", 'max_length': '55'}),
'success': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'type': ('django.db.models.fields.CharField', [], {'default': "'html'", 'max_length': '55'}),
'version': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'builds'", 'null': 'True', 'to': "orm['builds.Version']"})
},
'builds.version': {
'Meta': {'ordering': "['-verbose_name']", 'unique_together': "[('project', 'slug')]", 'object_name': 'Version'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'built': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'identifier': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'privacy_level': ('django.db.models.fields.CharField', [], {'default': "'public'", 'max_length': '20'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'versions'", 'to': "orm['projects.Project']"}),
'slug': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'uploaded': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'verbose_name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
},
'builds.versionalias': {
'Meta': {'object_name': 'VersionAlias'},
'from_slug': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'largest': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'aliases'", 'to': "orm['projects.Project']"}),
'to_slug': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'blank': 'True'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'projects.project': {
'Meta': {'ordering': "('slug',)", 'object_name': 'Project'},
'analytics_code': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
'conf_py_file': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'blank': 'True'}),
'copyright': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'crate_url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'default_branch': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}),
'default_version': ('django.db.models.fields.CharField', [], {'default': "'latest'", 'max_length': '255'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'django_packages_url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'documentation_type': ('django.db.models.fields.CharField', [], {'default': "'sphinx'", 'max_length': '20'}),
'featured': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'path': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'privacy_level': ('django.db.models.fields.CharField', [], {'default': "'public'", 'max_length': '20'}),
'project_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
'pub_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'related_projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['projects.Project']", 'null': 'True', 'through': "orm['projects.ProjectRelationship']", 'blank': 'True'}),
'repo': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'repo_type': ('django.db.models.fields.CharField', [], {'default': "'git'", 'max_length': '10'}),
'requirements_file': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}),
'skip': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255'}),
'suffix': ('django.db.models.fields.CharField', [], {'default': "'.rst'", 'max_length': '10'}),
'theme': ('django.db.models.fields.CharField', [], {'default': "'default'", 'max_length': '20'}),
'use_system_packages': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'use_virtualenv': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'projects'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
'version': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'version_privacy_level': ('django.db.models.fields.CharField', [], {'default': "'public'", 'max_length': '20'})
},
'projects.projectrelationship': {
'Meta': {'object_name': 'ProjectRelationship'},
'child': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'superprojects'", 'to': "orm['projects.Project']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'subprojects'", 'to': "orm['projects.Project']"})
},
'taggit.tag': {
'Meta': {'object_name': 'Tag'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'})
},
'taggit.taggeditem': {
'Meta': {'object_name': 'TaggedItem'},
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_tagged_items'", 'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_items'", 'to': "orm['taggit.Tag']"})
}
}
complete_apps = ['builds']

View File

@ -1,10 +1,41 @@
from django.db import models
from projects.models import Project
from django.utils.translation import ugettext_lazy as _, ugettext
from guardian.shortcuts import assign, get_objects_for_user
from projects.models import Project
from projects import constants
from .constants import BUILD_STATE, BUILD_TYPES
class VersionManager(models.Manager):
def _filter_queryset(self, user, project, privacy_level):
if isinstance(privacy_level, basestring):
privacy_level = (privacy_level,)
queryset = Version.objects.filter(privacy_level__in=privacy_level)
if not user and not project:
return queryset
if user and user.is_authenticated():
# Add in possible user-specific views
user_queryset = get_objects_for_user(user, 'builds.view_version')
queryset = user_queryset | queryset
if project:
# Filter by project if requested
queryset = queryset.filter(project=project)
return queryset.filter(active=True)
def public(self, user=None, project=None, *args, **kwargs):
queryset = self._filter_queryset(user, project, privacy_level=constants.PUBLIC)
return queryset.filter(*args, **kwargs)
def protected(self, user=None, project=None, *args, **kwargs):
queryset = self._filter_queryset(user, project, privacy_level=(constants.PUBLIC, constants.PROTECTED))
return queryset.filter(*args, **kwargs)
def private(self, user=None, project=None, *args, **kwargs):
queryset = self._filter_queryset(user, project, privacy_level=constants.PRIVATE)
return queryset.filter(*args, **kwargs)
class Version(models.Model):
project = models.ForeignKey(Project, verbose_name=_('Project'), related_name='versions')
identifier = models.CharField(_('Identifier'), max_length=255) # used by the vcs backend
@ -13,10 +44,19 @@ class Version(models.Model):
active = models.BooleanField(_('Active'), default=False)
built = models.BooleanField(_('Built'), default=False)
uploaded = models.BooleanField(_('Uploaded'), default=False)
privacy_level = models.CharField(_('Privacy Level'), max_length=20,
choices=constants.PRIVACY_CHOICES, default='public',
help_text=_("Level of privacy for this Version."))
objects = VersionManager()
class Meta:
unique_together = [('project', 'slug')]
ordering = ['-verbose_name']
permissions = (
# Translators: Permission around whether a user can view the version
('view_version', _('View Version')),
)
def __unicode__(self):
return ugettext(u"Version %(version)s of %(project)s (%(pk)s)" % {
@ -30,6 +70,16 @@ class Version(models.Model):
return ''
return self.project.get_docs_url(version_slug=self.slug)
def save(self, *args, **kwargs):
"""
Add permissions to the Version for all owners on save.
"""
obj = super(Version, self).save(*args, **kwargs)
for owner in self.project.users.all():
assign('view_version', owner, self)
return obj
class VersionAlias(models.Model):
project = models.ForeignKey(Project, verbose_name=_('Project'), related_name='aliases')

View File

@ -3,16 +3,18 @@ from django.http import HttpResponsePermanentRedirect
from django.shortcuts import get_object_or_404
from django.views.generic.list_detail import object_list, object_detail
from guardian.decorators import permission_required_or_403
from taggit.models import Tag
from builds.models import Build
from builds.filters import BuildFilter
from projects.models import Project
from taggit.models import Tag
def build_list(request, project_slug=None, tag=None):
"""Show a list of builds.
"""
queryset = Build.objects.all()
filter = BuildFilter(request.GET, queryset=queryset)
if tag:
tag = get_object_or_404(Tag, slug=tag)
@ -20,7 +22,7 @@ def build_list(request, project_slug=None, tag=None):
else:
tag = None
project = get_object_or_404(Project, slug=project_slug)
project = get_object_or_404(Project.objects.protected(request.user), slug=project_slug)
queryset = queryset.filter(project=project)
filter = BuildFilter(request.GET, queryset=queryset)
active_builds = queryset.exclude(state="finished").values('id')
@ -40,8 +42,8 @@ def build_list(request, project_slug=None, tag=None):
def build_detail(request, project_slug, pk):
"""Show the details of a particular build.
"""
project = get_object_or_404(Project, slug=project_slug)
queryset = Build.objects.filter(project=project)
project = get_object_or_404(Project.objects.protected(request.user), slug=project_slug)
return object_detail(
request,

View File

@ -7,7 +7,7 @@
"first_name": "",
"last_name": "",
"is_active": true,
"is_superuser": true,
"is_superuser": false,
"is_staff": true,
"last_login": "2010-08-14 01:51:05",
"groups": [],
@ -34,5 +34,23 @@
"email": "e@etest.co",
"date_joined": "2010-08-14 01:50:58"
}
},
{
"pk": 3,
"model": "auth.user",
"fields": {
"username": "super",
"first_name": "",
"last_name": "",
"is_active": true,
"is_superuser": true,
"is_staff": true,
"last_login": "2010-08-14 01:51:05",
"groups": [],
"user_permissions": [],
"password": "sha1$035cb$156ad6cb44332fb4f24bcb634142a67435be0b37",
"email": "e@e.co",
"date_joined": "2010-08-14 01:50:58"
}
}
]

View File

@ -1,7 +1,6 @@
import logging
import getpass
import os
from django.conf import settings
log = logging.getLogger(__name__)
@ -49,3 +48,5 @@ def run_on_app_servers(command):
else:
ret = os.system(command)
return ret

View File

@ -14,6 +14,7 @@ from django.views.static import serve
from django.views.generic import TemplateView
from haystack.query import EmptySearchQuerySet
from guardian.shortcuts import get_objects_for_user
from builds.models import Build
from core.forms import FacetedSearchForm
@ -31,11 +32,11 @@ log = logging.getLogger(__name__)
def homepage(request):
#latest_projects = Project.objects.filter(builds__isnull=False).annotate(max_date=Max('builds__date')).order_by('-max_date')[:10]
latest_projects = Project.objects.order_by('-modified_date')[:10]
latest = Project.objects.public(request.user).order_by('-modified_date')[:10]
featured = Project.objects.filter(featured=True)
return render_to_response('homepage.html',
{'project_list': latest_projects,
{'project_list': latest,
'featured_list': featured,
#'updated_list': updated
},

View File

@ -5,6 +5,7 @@ and related models.
from builds.models import Version
from django.contrib import admin
from projects.models import Project, ImportedFile, ProjectRelationship
from guardian.admin import GuardedModelAdmin
class ProjectRelationshipInline(admin.TabularInline):
model = ProjectRelationship
@ -14,7 +15,7 @@ class VersionInline(admin.TabularInline):
model = Version
class ProjectAdmin(admin.ModelAdmin):
class ProjectAdmin(GuardedModelAdmin):
prepopulated_fields = {'slug': ('name',)}
list_display = ('name', 'repo', 'repo_type', 'featured', 'theme')
list_filter = ('repo_type', 'featured')

View File

@ -70,3 +70,14 @@ REPO_CHOICES = (
('hg', _('Mercurial')),
('bzr', _('Bazaar')),
)
PUBLIC = 'public'
PROTECTED = 'protected'
PRIVATE = 'private'
PRIVACY_CHOICES = (
(PUBLIC, _('Public')),
(PROTECTED, _('Protected')),
(PRIVATE, _('Private')),
)

View File

@ -3,6 +3,9 @@ from django.conf import settings
from django.template.defaultfilters import slugify
from django.template.loader import render_to_string
from django.utils.translation import ugettext_lazy as _
from django.utils.safestring import mark_safe
from projects import constants
from projects.models import Project
from projects.tasks import update_docs
@ -26,10 +29,18 @@ class ImportProjectForm(ProjectForm):
class Meta:
model = Project
fields = ('name', 'repo', 'repo_type', 'description', 'project_url',
'tags', 'default_branch', 'default_version', 'use_virtualenv',
'use_system_packages', 'conf_py_file', 'requirements_file',
'analytics_code', 'documentation_type')
fields = (
# Important
'name', 'repo', 'repo_type', 'description',
# Not as important
'project_url', 'tags', 'default_branch', 'default_version', 'conf_py_file',
# Privacy
'privacy_level', 'version_privacy_level',
# Python specific
'use_virtualenv', 'use_system_packages', 'requirements_file',
# Fringe
'analytics_code', 'documentation_type', 'tags'
)
def clean_repo(self):
repo = self.cleaned_data.get('repo', '').strip()
@ -63,7 +74,7 @@ class DualCheckboxWidget(forms.CheckboxInput):
def render(self, name, value, attrs=None):
checkbox = super(DualCheckboxWidget, self).render(name, value, attrs)
icon = self.render_icon()
return u'%s%s' % (checkbox, icon)
return mark_safe(u'%s%s' % (checkbox, icon))
def render_icon(self):
context = {
@ -87,9 +98,11 @@ class BaseVersionsForm(forms.Form):
def save_version(self, version):
new_value = self.cleaned_data.get('version-%s' % version.slug, None)
if new_value is None or new_value == version.active:
privacy_level = self.cleaned_data.get('privacy-%s' % version.slug, None)
if (new_value is None or new_value == version.active) and (privacy_level is None or privacy_level == version.privacy_level):
return
version.active = new_value
version.privacy_level = privacy_level
version.save()
if version.active and not version.built and not version.uploaded:
update_docs.delay(self.project.pk, record=True, version_pk=version.pk)
@ -104,18 +117,25 @@ def build_versions_form(project):
if active.exists():
choices = [(version.slug, version.verbose_name) for version in active]
attrs['default-version'] = forms.ChoiceField(
label=_("Choose the default version for this project"),
label=_("Default Version"),
choices=choices,
initial=project.get_default_version(),
)
for version in versions_qs:
field_name = 'version-%s' % version.slug
privacy_name = 'privacy-%s' % version.slug
attrs[field_name] = forms.BooleanField(
label=version.verbose_name,
widget=DualCheckboxWidget(version),
initial=version.active,
required=False,
)
attrs[privacy_name] = forms.ChoiceField(
# This isn't a real label, but just a slug for the template
label="privacy",
choices=constants.PRIVACY_CHOICES,
initial=version.privacy_level,
)
return type('VersionsForm', (BaseVersionsForm,), attrs)

View File

@ -0,0 +1,151 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Deleting model 'FileRevision'
db.delete_table('projects_filerevision')
# Deleting model 'File'
db.delete_table('projects_file')
# Adding field 'Project.privacy_level'
db.add_column('projects_project', 'privacy_level',
self.gf('django.db.models.fields.CharField')(default='public', max_length=20),
keep_default=False)
def backwards(self, orm):
# Adding model 'FileRevision'
db.create_table('projects_filerevision', (
('comment', self.gf('django.db.models.fields.TextField')(blank=True)),
('revision_number', self.gf('django.db.models.fields.IntegerField')()),
('file', self.gf('django.db.models.fields.related.ForeignKey')(related_name='revisions', to=orm['projects.File'])),
('created_date', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
('diff', self.gf('django.db.models.fields.TextField')(blank=True)),
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('is_reverted', self.gf('django.db.models.fields.BooleanField')(default=False)),
))
db.send_create_signal('projects', ['FileRevision'])
# Adding model 'File'
db.create_table('projects_file', (
('content', self.gf('django.db.models.fields.TextField')()),
('project', self.gf('django.db.models.fields.related.ForeignKey')(related_name='files', to=orm['projects.Project'])),
('slug', self.gf('django.db.models.fields.SlugField')(max_length=50)),
('status', self.gf('django.db.models.fields.PositiveSmallIntegerField')(default=1)),
('denormalized_path', self.gf('django.db.models.fields.CharField')(max_length=255)),
('parent', self.gf('django.db.models.fields.related.ForeignKey')(related_name='children', null=True, to=orm['projects.File'], blank=True)),
('ordering', self.gf('django.db.models.fields.PositiveSmallIntegerField')(default=1)),
('heading', self.gf('django.db.models.fields.CharField')(max_length=255)),
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
))
db.send_create_signal('projects', ['File'])
# Deleting field 'Project.privacy_level'
db.delete_column('projects_project', 'privacy_level')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 10, 13, 22, 28, 56, 789520)'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 10, 13, 22, 28, 56, 789253)'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'projects.importedfile': {
'Meta': {'object_name': 'ImportedFile'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'md5': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'path': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'imported_files'", 'to': "orm['projects.Project']"}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'})
},
'projects.project': {
'Meta': {'ordering': "('slug',)", 'object_name': 'Project'},
'analytics_code': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
'conf_py_file': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'blank': 'True'}),
'copyright': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'crate_url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'default_branch': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}),
'default_version': ('django.db.models.fields.CharField', [], {'default': "'latest'", 'max_length': '255'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'django_packages_url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'documentation_type': ('django.db.models.fields.CharField', [], {'default': "'sphinx'", 'max_length': '20'}),
'featured': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'path': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'privacy_level': ('django.db.models.fields.CharField', [], {'default': "'public'", 'max_length': '20'}),
'project_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
'pub_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'related_projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['projects.Project']", 'null': 'True', 'through': "orm['projects.ProjectRelationship']", 'blank': 'True'}),
'repo': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'repo_type': ('django.db.models.fields.CharField', [], {'default': "'git'", 'max_length': '10'}),
'requirements_file': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}),
'skip': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255'}),
'suffix': ('django.db.models.fields.CharField', [], {'default': "'.rst'", 'max_length': '10'}),
'theme': ('django.db.models.fields.CharField', [], {'default': "'default'", 'max_length': '20'}),
'use_system_packages': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'use_virtualenv': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'projects'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
'version': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'})
},
'projects.projectrelationship': {
'Meta': {'object_name': 'ProjectRelationship'},
'child': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'superprojects'", 'to': "orm['projects.Project']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'subprojects'", 'to': "orm['projects.Project']"})
},
'taggit.tag': {
'Meta': {'object_name': 'Tag'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'})
},
'taggit.taggeditem': {
'Meta': {'object_name': 'TaggedItem'},
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_tagged_items'", 'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_items'", 'to': "orm['taggit.Tag']"})
}
}
complete_apps = ['projects']

View File

@ -0,0 +1,120 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'Project.version_privacy_level'
db.add_column('projects_project', 'version_privacy_level',
self.gf('django.db.models.fields.CharField')(default='public', max_length=20),
keep_default=False)
def backwards(self, orm):
# Deleting field 'Project.version_privacy_level'
db.delete_column('projects_project', 'version_privacy_level')
models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 10, 13, 23, 55, 17, 885486)'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 10, 13, 23, 55, 17, 885212)'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'projects.importedfile': {
'Meta': {'object_name': 'ImportedFile'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'md5': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'path': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'imported_files'", 'to': "orm['projects.Project']"}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'})
},
'projects.project': {
'Meta': {'ordering': "('slug',)", 'object_name': 'Project'},
'analytics_code': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
'conf_py_file': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255', 'blank': 'True'}),
'copyright': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'crate_url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'default_branch': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}),
'default_version': ('django.db.models.fields.CharField', [], {'default': "'latest'", 'max_length': '255'}),
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'django_packages_url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
'documentation_type': ('django.db.models.fields.CharField', [], {'default': "'sphinx'", 'max_length': '20'}),
'featured': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'path': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'privacy_level': ('django.db.models.fields.CharField', [], {'default': "'public'", 'max_length': '20'}),
'project_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
'pub_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'related_projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['projects.Project']", 'null': 'True', 'through': "orm['projects.ProjectRelationship']", 'blank': 'True'}),
'repo': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'repo_type': ('django.db.models.fields.CharField', [], {'default': "'git'", 'max_length': '10'}),
'requirements_file': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}),
'skip': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255'}),
'suffix': ('django.db.models.fields.CharField', [], {'default': "'.rst'", 'max_length': '10'}),
'theme': ('django.db.models.fields.CharField', [], {'default': "'default'", 'max_length': '20'}),
'use_system_packages': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'use_virtualenv': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'projects'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
'version': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'version_privacy_level': ('django.db.models.fields.CharField', [], {'default': "'public'", 'max_length': '20'})
},
'projects.projectrelationship': {
'Meta': {'object_name': 'ProjectRelationship'},
'child': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'superprojects'", 'to': "orm['projects.Project']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'subprojects'", 'to': "orm['projects.Project']"})
},
'taggit.tag': {
'Meta': {'object_name': 'Tag'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'})
},
'taggit.taggeditem': {
'Meta': {'object_name': 'TaggedItem'},
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_tagged_items'", 'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_items'", 'to': "orm['taggit.Tag']"})
}
}
complete_apps = ['projects']

View File

@ -9,6 +9,8 @@ from django.db import models
from django.template.defaultfilters import slugify
from django.utils.translation import ugettext_lazy as _
from guardian.shortcuts import assign, get_objects_for_user
from projects import constants
from projects.exceptions import ProjectImportError
from projects.templatetags.projects_tags import sort_version_aware
@ -24,10 +26,44 @@ from vcs_support.utils import Lock
log = logging.getLogger(__name__)
class ProjectManager(models.Manager):
def _filter_queryset(self, user, privacy_level):
if isinstance(privacy_level, basestring):
privacy_level = (privacy_level,)
queryset = Project.objects.filter(privacy_level__in=privacy_level)
if not user:
return queryset
if user.is_authenticated():
# Add in possible user-specific views
user_queryset = get_objects_for_user(user, 'projects.view_project')
queryset = user_queryset | queryset
return queryset.filter(skip=False)
def live(self, *args, **kwargs):
base_qs = self.filter(skip=False)
return base_qs.filter(*args, **kwargs)
def public(self, user=None, *args, **kwargs):
"""
Query for projects, privacy_level == public, and skip = False
"""
queryset = self._filter_queryset(user, privacy_level=constants.PUBLIC)
return queryset.filter(*args, **kwargs)
def protected(self, user=None, *args, **kwargs):
"""
Query for projects, privacy_level != private, and skip = False
"""
queryset = self._filter_queryset(user, privacy_level=(constants.PUBLIC, constants.PROTECTED))
return queryset.filter(*args, **kwargs)
def private(self, user=None, *args, **kwargs):
"""
Query for projects, privacy_level != private, and skip = False
"""
queryset = self._filter_queryset(user, privacy_level=constants.PRIVATE)
return queryset.filter(*args, **kwargs)
class ProjectRelationship(models.Model):
parent = models.ForeignKey('Project', verbose_name=_('Parent'), related_name='subprojects')
child = models.ForeignKey('Project', verbose_name=_('Child'), related_name='superprojects')
@ -86,6 +122,12 @@ class Project(models.Model):
help_text=_("Give the virtual environment access to the global sites-packages dir"))
django_packages_url = models.CharField(_('Django Packages URL'), max_length=255, blank=True)
crate_url = models.CharField(_('Crate URL'), max_length=255, blank=True)
privacy_level = models.CharField(_('Privacy Level'), max_length=20,
choices=constants.PRIVACY_CHOICES, default='public',
help_text=_("Level of privacy that you want on the repository. Protected means public but not in listings."))
version_privacy_level = models.CharField(_('Version Privacy Level'), max_length=20,
choices=constants.PRIVACY_CHOICES, default='public',
help_text=_("Default level of privacy you want on built versions of documentation."))
#Subprojects
related_projects = models.ManyToManyField('self', verbose_name=_('Related projects'), blank=True, null=True, symmetrical=False, through=ProjectRelationship)
@ -95,6 +137,10 @@ class Project(models.Model):
class Meta:
ordering = ('slug',)
permissions = (
# Translators: Permission around whether a user can view the project
('view_project', _('View Project')),
)
def __unicode__(self):
return self.name
@ -117,7 +163,10 @@ class Project(models.Model):
self.slug = slugify(self.name)
if self.slug == '':
raise Exception(_("Model must have slug"))
super(Project, self).save(*args, **kwargs)
obj = super(Project, self).save(*args, **kwargs)
for owner in self.users.all():
assign('view_project', owner, self)
return obj
def get_absolute_url(self):
return reverse('projects_detail', args=[self.slug])

View File

@ -10,6 +10,8 @@ from django.shortcuts import get_object_or_404, render_to_response
from django.template import RequestContext
from django.views.generic.list_detail import object_list
from guardian.shortcuts import assign
from builds.forms import AliasForm
from projects.forms import (ImportProjectForm, build_versions_form,
build_upload_html_form, SubprojectForm)
@ -123,6 +125,7 @@ def project_import(request):
if request.method == 'POST' and form.is_valid():
project = form.save()
form.instance.users.add(request.user)
assign('view_project', request.user, project)
project_manage = reverse('projects_detail', args=[project.slug])
return HttpResponseRedirect(project_manage + '?docs_not_built=True')

View File

@ -9,19 +9,21 @@ from django.template import RequestContext
from django.views.generic.list_detail import object_list
from django.utils.datastructures import SortedDict
from guardian.decorators import permission_required_or_403
from guardian.shortcuts import get_objects_for_user
from taggit.models import Tag
from core.views import serve_docs
from projects.models import Project
from projects.utils import highest_version
from taggit.models import Tag
def project_index(request, username=None, tag=None):
"""
The list of projects, which will optionally filter by user or tag,
in which case a 'person' or 'tag' will be added to the context
"""
queryset = Project.objects.live()
queryset = Project.objects.public(request.user)
if username:
user = get_object_or_404(User, username=username)
queryset = queryset.filter(user=user)
@ -42,15 +44,19 @@ def project_index(request, username=None, tag=None):
template_object_name='project',
)
def project_detail(request, project_slug):
"""
A detail view for a project with various dataz
"""
project = get_object_or_404(Project, slug=project_slug)
queryset = Project.objects.protected(request.user)
project = get_object_or_404(queryset, slug=project_slug)
versions = project.versions.public(request.user, project)
return render_to_response(
'projects/project_detail.html',
{
'project': project,
'versions': versions,
},
context_instance=RequestContext(request),
)
@ -59,7 +65,7 @@ def project_downloads(request, project_slug):
"""
A detail view for a project with various dataz
"""
project = get_object_or_404(Project, slug=project_slug)
project = get_object_or_404(Project.objects.protected(request.user), slug=project_slug)
versions = project.ordered_active_versions()
version_data = SortedDict()
for version in versions:

View File

@ -5,3 +5,4 @@ from test_backend import *
from test_celery import *
from test_hacks import *
from test_post_commit_hooks import *
from test_privacy import *

View File

@ -18,14 +18,14 @@ class APIBuildTests(TestCase):
resp = self.client.post('/api/v1/build/',
data=json.dumps(post_data),
content_type='application/json',
HTTP_AUTHORIZATION='Basic %s' % base64.b64encode('eric:test')
HTTP_AUTHORIZATION='Basic %s' % base64.b64encode('super:test')
)
self.assertEqual(resp.status_code, 201)
self.assertEqual(resp['location'],
'http://testserver/api/v1/build/1/')
resp = self.client.get('/api/v1/build/1/',
data={'format': 'json'},
HTTP_AUTHORIZATION='Basic %s' % base64.b64encode('eric:test')
HTTP_AUTHORIZATION='Basic %s' % base64.b64encode('super:test')
)
self.assertEqual(resp.status_code, 200)
obj = json.loads(resp.content)
@ -46,7 +46,7 @@ class APITests(TestCase):
resp = self.client.post('/api/v1/project/',
data=json.dumps(post_data),
content_type='application/json',
HTTP_AUTHORIZATION='Basic %s' % base64.b64encode('eric:test')
HTTP_AUTHORIZATION='Basic %s' % base64.b64encode('super:test')
)
self.assertEqual(resp.status_code, 201)
self.assertEqual(resp['location'],

View File

@ -0,0 +1,264 @@
import logging
import json
from django.test import TestCase
from builds.models import Version
from projects.models import Project
from projects import tasks
log = logging.getLogger(__name__)
class PrivacyTests(TestCase):
fixtures = ["eric"]
def tearDown(self):
tasks.update_docs = self.old_bd
def setUp(self):
self.old_bd = tasks.update_docs
def mock(*args, **kwargs):
pass
#log.info("Mocking for great profit and speed.")
tasks.update_docs.delay = mock
def _create_kong(self, privacy_level='private', version_privacy_level='private'):
self.client.login(username='eric', password='test')
log.info("Making kong with privacy: %s and version privacy: %s" % (privacy_level, version_privacy_level))
r = self.client.post(
'/dashboard/import/',
{'repo_type': 'git', 'name': 'Django Kong',
'tags': 'big, fucking, monkey', 'default_branch': '',
'project_url': 'http://django-kong.rtfd.org',
'repo': 'https://github.com/ericholscher/django-kong',
'csrfmiddlewaretoken': '34af7c8a5ba84b84564403a280d9a9be',
'default_version': 'latest',
'privacy_level': privacy_level,
'version_privacy_level': version_privacy_level,
'description': 'OOHHH AH AH AH KONG SMASH',
'documentation_type': 'sphinx'})
self.assertEqual(r.status_code, 302)
self.assertAlmostEqual(Project.objects.count(), 1)
r = self.client.get('/projects/django-kong/')
self.assertEqual(r.status_code, 200)
return Project.objects.get(slug='django-kong')
def test_private_repo(self):
"""
Check that private projects don't show up in: builds, downloads, detail, homepage
"""
kong = self._create_kong('private', 'private')
self.client.login(username='eric', password='test')
r = self.client.get('/')
self.assertTrue('Django Kong' in r.content)
r = self.client.get('/projects/django-kong/')
self.assertEqual(r.status_code, 200)
r = self.client.get('/builds/django-kong/')
self.assertEqual(r.status_code, 200)
r = self.client.get('/projects/django-kong/downloads/')
self.assertEqual(r.status_code, 200)
self.client.login(username='tester', password='test')
r = self.client.get('/')
self.assertTrue('Django Kong' not in r.content)
r = self.client.get('/projects/django-kong/')
self.assertEqual(r.status_code, 404)
r = self.client.get('/builds/django-kong/')
self.assertEqual(r.status_code, 404)
r = self.client.get('/projects/django-kong/downloads/')
self.assertEqual(r.status_code, 404)
def test_protected_repo(self):
"""
Check that protected projects don't show up in: builds, downloads, detail, project list
"""
kong = self._create_kong('protected', 'protected')
self.client.login(username='eric', password='test')
r = self.client.get('/')
self.assertTrue('Django Kong' in r.content)
r = self.client.get('/projects/')
self.assertTrue('Django Kong' in r.content)
r = self.client.get('/projects/django-kong/')
self.assertEqual(r.status_code, 200)
r = self.client.get('/builds/django-kong/')
self.assertEqual(r.status_code, 200)
r = self.client.get('/projects/django-kong/downloads/')
self.assertEqual(r.status_code, 200)
self.client.login(username='tester', password='test')
r = self.client.get('/')
self.assertTrue('Django Kong' not in r.content)
r = self.client.get('/projects/')
self.assertTrue('Django Kong' not in r.content)
r = self.client.get('/projects/django-kong/')
self.assertEqual(r.status_code, 200)
r = self.client.get('/builds/django-kong/')
self.assertEqual(r.status_code, 200)
r = self.client.get('/projects/django-kong/downloads/')
self.assertEqual(r.status_code, 200)
def test_public_repo(self):
"""
Check that public projects show up in: builds, downloads, detail, homepage
"""
kong = self._create_kong('public', 'public')
self.client.login(username='eric', password='test')
r = self.client.get('/')
self.assertTrue('Django Kong' in r.content)
r = self.client.get('/projects/django-kong/')
self.assertEqual(r.status_code, 200)
r = self.client.get('/builds/django-kong/')
self.assertEqual(r.status_code, 200)
r = self.client.get('/projects/django-kong/downloads/')
self.assertEqual(r.status_code, 200)
self.client.login(username='tester', password='test')
r = self.client.get('/')
self.assertTrue('Django Kong' in r.content)
r = self.client.get('/projects/django-kong/')
self.assertEqual(r.status_code, 200)
r = self.client.get('/builds/django-kong/')
self.assertEqual(r.status_code, 200)
r = self.client.get('/projects/django-kong/downloads/')
self.assertEqual(r.status_code, 200)
def test_private_branch(self):
kong = self._create_kong('public', 'private')
self.client.login(username='eric', password='test')
kong_1 = Version.objects.create(project=kong, identifier='test id', verbose_name='test verbose', slug='test-slug')
r = self.client.post('/dashboard/django-kong/versions/', {'version-test-slug': 'on', 'privacy-test-slug': 'private' })
self.assertEqual(Version.objects.count(), 1)
self.assertEqual(Version.objects.all()[0].privacy_level, 'private')
r = self.client.get('/projects/django-kong/')
self.assertTrue('test-slug' in r.content)
# Make sure it doesn't show up as tester
self.client.login(username='tester', password='test')
r = self.client.get('/projects/django-kong/')
self.assertTrue('test-slug' not in r.content)
def test_protected_branch(self):
kong = self._create_kong('public', 'protected')
self.client.login(username='eric', password='test')
kong_1 = Version.objects.create(project=kong, identifier='test id', verbose_name='test verbose', slug='test-slug')
r = self.client.post('/dashboard/django-kong/versions/', {'version-test-slug': 'on', 'privacy-test-slug': 'protected'})
self.assertEqual(Version.objects.count(), 1)
self.assertEqual(Version.objects.all()[0].privacy_level, 'protected')
r = self.client.get('/projects/django-kong/')
self.assertTrue('test-slug' in r.content)
# Make sure it doesn't show up as tester
self.client.login(username='tester', password='test')
r = self.client.get('/projects/django-kong/')
self.assertTrue('test-slug' not in r.content)
def test_public_branch(self):
kong = self._create_kong('public', 'public')
self.client.login(username='eric', password='test')
kong_1 = Version.objects.create(project=kong, identifier='test id', verbose_name='test verbose', slug='test-slug')
r = self.client.post('/dashboard/django-kong/versions/', {'version-test-slug': 'on', 'privacy-test-slug': 'public'})
self.assertEqual(Version.objects.count(), 1)
self.assertEqual(Version.objects.all()[0].privacy_level, 'public')
r = self.client.get('/projects/django-kong/')
self.assertTrue('test-slug' in r.content)
# Make sure it doesn't show up as tester
self.client.login(username='tester', password='test')
r = self.client.get('/projects/django-kong/')
self.assertTrue('test-slug' in r.content)
def test_public_repo_api(self):
kong = self._create_kong('public', 'public')
self.client.login(username='eric', password='test')
resp = self.client.get("http://testserver/api/v1/project/django-kong/",
data={"format": "json"}
)
self.assertEqual(resp.status_code, 200)
resp = self.client.get("http://testserver/api/v1/project/",
data={"format": "json"}
)
self.assertEqual(resp.status_code, 200)
data = json.loads(resp.content)
self.assertEqual(data['meta']['total_count'], 1)
self.client.login(username='tester', password='test')
resp = self.client.get("http://testserver/api/v1/project/django-kong/",
data={"format": "json"}
)
self.assertEqual(resp.status_code, 200)
resp = self.client.get("http://testserver/api/v1/project/",
data={"format": "json"}
)
self.assertEqual(resp.status_code, 200)
data = json.loads(resp.content)
self.assertEqual(data['meta']['total_count'], 1)
def test_protected_repo_api(self):
kong = self._create_kong('protected', 'protected')
self.client.login(username='eric', password='test')
resp = self.client.get("http://testserver/api/v1/project/django-kong/",
data={"format": "json"}
)
self.assertEqual(resp.status_code, 200)
resp = self.client.get("http://testserver/api/v1/project/",
data={"format": "json"}
)
data = json.loads(resp.content)
self.assertEqual(data['meta']['total_count'], 1)
self.client.login(username='tester', password='test')
resp = self.client.get("http://testserver/api/v1/project/",
data={"format": "json"}
)
data = json.loads(resp.content)
self.assertEqual(data['meta']['total_count'], 0)
# Need to figure out how to properly filter the detail view in tastypie.
# Protected stuff won't show up in detail pages on the API currently.
"""
resp = self.client.get("http://testserver/api/v1/project/django-kong/",
data={"format": "json"}
)
self.assertEqual(resp.status_code, 200)
"""
def test_private_repo_api(self):
kong = self._create_kong('private', 'private')
self.client.login(username='eric', password='test')
resp = self.client.get("http://testserver/api/v1/project/django-kong/",
data={"format": "json"}
)
self.assertEqual(resp.status_code, 200)
resp = self.client.get("http://testserver/api/v1/project/",
data={"format": "json"}
)
self.assertEqual(resp.status_code, 200)
data = json.loads(resp.content)
self.assertEqual(data['meta']['total_count'], 1)
self.client.login(username='tester', password='test')
resp = self.client.get("http://testserver/api/v1/project/django-kong/",
data={"format": "json"}
)
self.assertEqual(resp.status_code, 404)
resp = self.client.get("http://testserver/api/v1/project/",
data={"format": "json"}
)
self.assertEqual(resp.status_code, 200)
data = json.loads(resp.content)
self.assertEqual(data['meta']['total_count'], 0)

View File

@ -19,6 +19,8 @@ class Testmaker(TestCase):
'repo': 'https://github.com/ericholscher/django-kong',
'csrfmiddlewaretoken': '34af7c8a5ba84b84564403a280d9a9be',
'default_version': 'latest',
'privacy_level': 'public',
'version_privacy_level': 'public',
'description': 'OOHHH AH AH AH KONG SMASH',
'documentation_type': 'sphinx'})
self.assertEqual(r.status_code, 302)

View File

@ -27,6 +27,11 @@ LOGS_ROOT = os.path.join(SITE_ROOT, 'logs')
MEDIA_ROOT = '%s/media/' % (SITE_ROOT)
MEDIA_URL = '/media/'
ADMIN_MEDIA_PREFIX = '/media/admin/'
# For 1.4
STATIC_ROOT = ''
STATIC_URL = '/static/'
STATICFILES_DIRS = ()
STATICFILES_FINDERS = ()
CACHE_BACKEND = 'memcached://localhost:11211/'
CACHE_KEY_PREFIX = 'docs'
@ -97,11 +102,11 @@ INSTALLED_APPS = [
'registration',
'profiles',
'taggit',
'south',
#'south',
'basic.flagging',
'djcelery',
'djangosecure',
#'celery_haystack',
'guardian',
#daniellindsleyrocksdahouse
'haystack',
@ -149,6 +154,10 @@ backup_count = 1000
if DEBUG:
backup_count = 2
# Guardian Settings
GUARDIAN_RAISE_403 = True
ANONYMOUS_USER_ID = -1
LOGGING = {
'version': 1,
'disable_existing_loggers': True,

View File

@ -25,16 +25,26 @@
<br>
<!-- BEGIN search bar -->
<h3>{% trans "Privacy Level" %}</h3>
<div id="privacy_level">
<p>
{{ project.get_privacy_level_display }}
</p>
</div>
{% if project.active_versions.count %}
{% if versions %}
<h3> {% trans "Versions" %} </h3>
<p>
{% for version in project.ordered_active_versions %}
{% for version in versions %}
<div style="float: left;" class="version_left">
{% if version.uploaded or version.built %}
{# Link to the docs #}
<a href="{{ version.get_absolute_url }}">{{ version.slug }}</a>
{% if request.user in project.users.all %}
({{ version.get_privacy_level_display }})
{% endif %}
{% else %}
{% blocktrans with version.slug as slug %}{{ slug }} (Build Failed){% endblocktrans %}
{% endif %}

View File

@ -1,15 +1,11 @@
{% load i18n %}
{% if built %}
<a href="{{ url }}">{% trans "(Built)" %}</a>
{#<img src="{{ MEDIA_URL }}images/icon-built.png" alt="{% trans "built" %}" />#}
<a href="{{ url }}">({% trans "Built" %})</a>
{% else %}
{% if uploaded %}
<a href="{{ url }}">{% trans "(Uploaded)" %}</a>
{#<img src="{{ MEDIA_URL }}images/icon-not-built.png" alt="{% trans "not built" %}" />#}
<a href="{{ url }}">({% trans "Uploaded" %})</a>
{% else %}
{% trans "(Not built)" %}
{#<img src="{{ MEDIA_URL }}images/icon-not-built.png" alt="{% trans "not built" %}" />#}
({% trans "Not built" %})
{% endif %}
{% endif %}

View File

@ -16,7 +16,23 @@
{% trans "Choose which versions you would like to publish besides the latest revision." %}
</p>
<form method="post" action=".">{% csrf_token %}
{{ form.as_p }}
{% for field in form %}
{% if forloop.first %}
{# For choosing the default version #}
<h5> {{ field.label }} </h5>
{{ field }}
{% else %}
{% if field.label != "privacy" %}
<h3> {{ field.label}} </h3>
{{ field }}
{% else %}
{{ field }}
{% endif %}
{% endif %}
<br>
{% endfor %}
<p>
<input style="display: inline;" type="submit" value="{% trans "Submit" %}">
</p>