Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'master' of git://github.com/ask/chishop

  • Loading branch information...
commit eb834c564045a07e0014c5754231a857f13db663 2 parents 93d46f7 + 1e2fdc9
@bshi bshi authored
View
3  .gitignore
@@ -5,6 +5,7 @@
*.sqlite-journal
settings_local.py
.*.sw[po]
+*.kpf
dist/
*.egg-info
doc/__build/*
@@ -14,4 +15,4 @@ parts
eggs
bin
developer-eggs
-downloads
+downloads
View
3  AUTHORS
@@ -4,3 +4,6 @@ Russell Sim <russell.sim@gmail.com>
Brian Rosner <brosner@gmail.com>
Hugo Lopes Tavares <hltbra@gmail.com>
Sverre Johansen <sverre.johansen@gmail.com>
+Bo Shi <bs@alum.mit.edu>
+Carl Meyer <carl@dirtcircle.com>
+Vinícius das Chagas Silva <vinimaster@gmail.com>
View
7 README
@@ -27,6 +27,13 @@ Run the PyPI server
Please note that ``chishop/media/dists`` has to be writable by the
user the web-server is running as.
+In production
+-------------
+
+You may want to copy the file ``chishop/production_example.py`` and modify
+for use as your production settings; you will also need to modify
+``bin/django.wsgi`` to refer to your production settings.
+
Using Setuptools
================
View
1  TODO
@@ -11,7 +11,6 @@ PyPI feature replication
I'm not sure what the difference between a co-owner and maintainer is,
maybe it's just a label.
* Package author admin interface (submit, edit, view)
-* Search
* Documentation upload
* Ratings
* Random Monty Python quotes :-)
View
2  buildout.cfg
@@ -8,7 +8,7 @@ eggs = pkginfo
[django]
recipe = djangorecipe
version = 1.1.1
-settings = development
+settings = settings
eggs = ${buildout:eggs}
test = djangopypi
project = chishop
View
0  chishop/conf/__init__.py
No changes.
View
111 chishop/conf/default.py
@@ -0,0 +1,111 @@
+# Django settings for djangopypi project.
+import os
+
+ADMINS = (
+ # ('Your Name', 'your_email@domain.com'),
+)
+
+# Allow uploading a new distribution file for a project version
+# if a file of that type already exists.
+#
+# The default on PyPI is to not allow this, but it can be real handy
+# if you're sloppy.
+DJANGOPYPI_ALLOW_VERSION_OVERWRITE = False
+DJANGOPYPI_RELEASE_UPLOAD_TO = 'dists'
+
+# change to False if you do not want Django's default server to serve static pages
+LOCAL_DEVELOPMENT = True
+
+REGISTRATION_OPEN = True
+ACCOUNT_ACTIVATION_DAYS = 7
+LOGIN_REDIRECT_URL = "/"
+
+EMAIL_HOST = ''
+DEFAULT_FROM_EMAIL = ''
+SERVER_EMAIL = DEFAULT_FROM_EMAIL
+
+MANAGERS = ADMINS
+
+DATABASE_ENGINE = ''
+DATABASE_NAME = ''
+DATABASE_USER = ''
+DATABASE_PASSWORD = ''
+DATABASE_HOST = ''
+DATABASE_PORT = ''
+
+# Local time zone for this installation. Choices can be found here:
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+# although not all choices may be available on all operating systems.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+TIME_ZONE = 'America/Chicago'
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+LANGUAGE_CODE = 'en-us'
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+# Absolute path to the directory that holds media.
+# Example: "/home/media/media.lawrence.com/"
+here = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
+MEDIA_ROOT = os.path.join(here, 'media')
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash if there is a path component (optional in other cases).
+# Examples: "http://media.lawrence.com", "http://example.com/media/"
+MEDIA_URL = '/media/'
+
+# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
+# trailing slash.
+# Examples: "http://foo.com/media/", "/media/".
+ADMIN_MEDIA_PREFIX = '/admin-media/'
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = 'w_#0r2hh)=!zbynb*gg&969@)sy#^-^ia3m*+sd4@lst$zyaxu'
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+ 'django.template.loaders.filesystem.load_template_source',
+ 'django.template.loaders.app_directories.load_template_source',
+# 'django.template.loaders.eggs.load_template_source',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+)
+
+ROOT_URLCONF = 'urls'
+
+TEMPLATE_CONTEXT_PROCESSORS = (
+ "django.core.context_processors.auth",
+ "django.core.context_processors.debug",
+ "django.core.context_processors.i18n",
+ "django.core.context_processors.media",
+ "django.core.context_processors.request",
+)
+
+TEMPLATE_DIRS = (
+ # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
+ # Always use forward slashes, even on Windows.
+ # Don't forget to use absolute paths, not relative paths.
+ os.path.join(os.path.dirname(os.path.dirname(__file__)), "templates"),
+)
+
+INSTALLED_APPS = (
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'django.contrib.admin',
+ 'django.contrib.markup',
+ 'django.contrib.admindocs',
+ 'registration',
+ 'djangopypi',
+)
View
23 chishop/development.py
@@ -1,23 +0,0 @@
-from settings import *
-import os
-
-DEBUG = True
-TEMPLATE_DEBUG = DEBUG
-LOCAL_DEVELOPMENT = True
-
-if LOCAL_DEVELOPMENT:
- import sys
- sys.path.append(os.path.dirname(__file__))
-
-ADMINS = (
- ('chishop', 'example@example.org'),
-)
-
-MANAGERS = ADMINS
-
-DATABASE_ENGINE = 'sqlite3'
-DATABASE_NAME = os.path.join(here, 'devdatabase.db')
-DATABASE_USER = ''
-DATABASE_PASSWORD = ''
-DATABASE_HOST = ''
-DATABASE_PORT = ''
View
4 chishop/media/style/djangopypi.css
@@ -0,0 +1,4 @@
+.search {
+ text-align:right;
+ margin-right: 10px;
+}
View
18 chishop/production_example.py
@@ -0,0 +1,18 @@
+from conf.default import *
+import os
+
+DEBUG = False
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = (
+ ('chishop', 'example@example.org'),
+)
+
+MANAGERS = ADMINS
+
+DATABASE_ENGINE = 'postgresql_psycopg2'
+DATABASE_NAME = 'chishop'
+DATABASE_USER = 'chishop'
+DATABASE_PASSWORD = 'chishop'
+DATABASE_HOST = ''
+DATABASE_PORT = ''
View
112 chishop/settings.py
@@ -1,113 +1,23 @@
-# Django settings for djangopypi project.
+from conf.default import *
import os
-ADMINS = (
- # ('Your Name', 'your_email@domain.com'),
-)
-
-# Allow uploading a new distribution file for a project version
-# if a file of that type already exists.
-#
-# The default on PyPI is to not allow this, but it can be real handy
-# if you're sloppy.
-DJANGOPYPI_ALLOW_VERSION_OVERWRITE = False
-DJANGOPYPI_RELEASE_UPLOAD_TO = 'dists'
-
-# change to False if you do not want Django's default server to serve static pages
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
LOCAL_DEVELOPMENT = True
-REGISTRATION_OPEN = True
-ACCOUNT_ACTIVATION_DAYS = 7
-LOGIN_REDIRECT_URL = "/"
+if LOCAL_DEVELOPMENT:
+ import sys
+ sys.path.append(os.path.dirname(__file__))
-EMAIL_HOST = ''
-DEFAULT_FROM_EMAIL = ''
-SERVER_EMAIL = DEFAULT_FROM_EMAIL
+ADMINS = (
+ ('chishop', 'example@example.org'),
+)
MANAGERS = ADMINS
-DATABASE_ENGINE = ''
-DATABASE_NAME = ''
+DATABASE_ENGINE = 'sqlite3'
+DATABASE_NAME = os.path.join(here, 'devdatabase.db')
DATABASE_USER = ''
DATABASE_PASSWORD = ''
DATABASE_HOST = ''
DATABASE_PORT = ''
-
-# Local time zone for this installation. Choices can be found here:
-# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
-# although not all choices may be available on all operating systems.
-# If running in a Windows environment this must be set to the same as your
-# system time zone.
-TIME_ZONE = 'America/Chicago'
-
-# Language code for this installation. All choices can be found here:
-# http://www.i18nguy.com/unicode/language-identifiers.html
-LANGUAGE_CODE = 'en-us'
-
-SITE_ID = 1
-
-# If you set this to False, Django will make some optimizations so as not
-# to load the internationalization machinery.
-USE_I18N = True
-
-# Absolute path to the directory that holds media.
-# Example: "/home/media/media.lawrence.com/"
-here = os.path.abspath(os.path.dirname(__file__))
-MEDIA_ROOT = os.path.join(here, 'media')
-
-# URL that handles the media served from MEDIA_ROOT. Make sure to use a
-# trailing slash if there is a path component (optional in other cases).
-# Examples: "http://media.lawrence.com", "http://example.com/media/"
-MEDIA_URL = 'media/'
-
-MEDIA_PREFIX = "/media/"
-
-# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
-# trailing slash.
-# Examples: "http://foo.com/media/", "/media/".
-ADMIN_MEDIA_PREFIX = '/admin-media/'
-
-# Make this unique, and don't share it with anybody.
-SECRET_KEY = 'w_#0r2hh)=!zbynb*gg&969@)sy#^-^ia3m*+sd4@lst$zyaxu'
-
-# List of callables that know how to import templates from various sources.
-TEMPLATE_LOADERS = (
- 'django.template.loaders.filesystem.load_template_source',
- 'django.template.loaders.app_directories.load_template_source',
-# 'django.template.loaders.eggs.load_template_source',
-)
-
-MIDDLEWARE_CLASSES = (
- 'django.middleware.common.CommonMiddleware',
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
-)
-
-ROOT_URLCONF = 'urls'
-
-TEMPLATE_CONTEXT_PROCESSORS = (
- "django.core.context_processors.auth",
- "django.core.context_processors.debug",
- "django.core.context_processors.i18n",
- "django.core.context_processors.media",
- "django.core.context_processors.request",
-)
-
-TEMPLATE_DIRS = (
- # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
- # Always use forward slashes, even on Windows.
- # Don't forget to use absolute paths, not relative paths.
- os.path.join(os.path.dirname(__file__), "templates"),
-)
-
-INSTALLED_APPS = (
- 'django.contrib.auth',
- 'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.sites',
- 'django.contrib.admin',
- 'django.contrib.markup',
- 'django.contrib.admindocs',
- 'registration',
- 'djangopypi',
-)
View
5 chishop/templates/base.html
@@ -2,6 +2,7 @@
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-au" xml:lang="en-au">
<head>
<link rel="stylesheet" type="text/css" href="{% block stylesheet %}{% load adminmedia %}{% admin_media_prefix %}css/base.css{% endblock %}"/>
+<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}style/djangopypi.css"/>
{% block extrastyle %}{% endblock %}
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<title>{% block title %}{% endblock %}</title>
@@ -20,6 +21,10 @@
{% block site_logo %}{% endblock %}
<h1 id="site-name">{% block site_name_header %}{% endblock %}</h1>
</div>
+
+ <div class="search">
+ {% include "djangopypi/search.html" %}
+ </div>
<div id="user-tools">
{% if user.is_authenticated %}
View
4 chishop/templates/djangopypi/search.html
@@ -0,0 +1,4 @@
+<form action='search' method='post'>
+ <input type="text" name="search_term" id="search_term">
+ <input type='submit' value=' Search '/>
+</form>
View
31 chishop/templates/djangopypi/search_results.html
@@ -0,0 +1,31 @@
+{% extends "base_site.html" %}
+
+{% block bread_crumbs_1 %}&rsaquo;Search{% endblock %}
+
+{% block content %}
+ {% ifnotequal search_term ''%}
+ <h1>Index of Packages Matching '{{ search_term }}'</h1>
+ {% else %}
+ <h1>You need to supply a search term.</h1>
+ {% endifnotequal %}
+ {% if dists %}
+ <table>
+ <thead>
+ <th>Updated</th>
+ <th>Package</th>
+ <th>Summary</th>
+ </thead>
+ <tbody>
+ {% for dist in dists %}
+ <tr>
+ <td>{{ dist.updated|date:"d/m/y" }}
+ <td><a href="{{ dist.get_pypi_absolute_url }}"/>{{ dist.name }}</a></td>
+ <td>{{ dist.summary|truncatewords:10 }}</td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ {% else %}
+ There were no matches.
+ {% endif %}
+{% endblock content %}
View
2  chishop/urls.py
@@ -10,7 +10,7 @@
# Serve static pages.
if settings.LOCAL_DEVELOPMENT:
urlpatterns += patterns("django.views",
- url(r"%s(?P<path>.*)$" % settings.MEDIA_URL[1:], "static.serve", {
+ url(r"^%s(?P<path>.*)$" % settings.MEDIA_URL[1:], "static.serve", {
"document_root": settings.MEDIA_ROOT}))
urlpatterns += patterns("",
View
3  djangopypi/forms.py
@@ -14,5 +14,4 @@ class Meta:
class ReleaseForm(forms.ModelForm):
class Meta:
model = Release
- exclude = ['project']
-
+ exclude = ['project']
View
51 djangopypi/http.py
@@ -1,4 +1,8 @@
from django.http import HttpResponse
+from django.core.files.uploadedfile import SimpleUploadedFile
+from django.utils.datastructures import MultiValueDict
+from django.contrib.auth import authenticate
+
class HttpResponseNotImplemented(HttpResponse):
status_code = 501
@@ -12,4 +16,51 @@ def __init__(self, realm):
self['WWW-Authenticate'] = 'Basic realm="%s"' % realm
+def parse_distutils_request(request):
+ raw_post_data = request.raw_post_data
+ sep = raw_post_data.splitlines()[1]
+ items = raw_post_data.split(sep)
+ post_data = {}
+ files = {}
+ for part in filter(lambda e: not e.isspace(), items):
+ item = part.splitlines()
+ if len(item) < 2:
+ continue
+ header = item[1].replace("Content-Disposition: form-data; ", "")
+ kvpairs = header.split(";")
+ headers = {}
+ for kvpair in kvpairs:
+ if not kvpair:
+ continue
+ key, value = kvpair.split("=")
+ headers[key] = value.strip('"')
+ if "name" not in headers:
+ continue
+ content = part[len("\n".join(item[0:2]))+2:len(part)-1]
+ if "filename" in headers:
+ file = SimpleUploadedFile(headers["filename"], content,
+ content_type="application/gzip")
+ files["distribution"] = [file]
+ elif headers["name"] in post_data:
+ post_data[headers["name"]].append(content)
+ else:
+ # Distutils sends UNKNOWN for empty fields (e.g platform)
+ # [russell.sim@gmail.com]
+ if content == 'UNKNOWN':
+ post_data[headers["name"]] = [None]
+ else:
+ post_data[headers["name"]] = [content]
+
+ return MultiValueDict(post_data), MultiValueDict(files)
+
+def login_basic_auth(request):
+ authentication = request.META.get("HTTP_AUTHORIZATION")
+ if not authentication:
+ return
+ (authmeth, auth) = authentication.split(' ', 1)
+ if authmeth.lower() != "basic":
+ return
+ auth = auth.strip().decode("base64")
+ username, password = auth.split(":", 1)
+ return authenticate(username=username, password=password)
View
22 djangopypi/tests.py
@@ -1,6 +1,10 @@
import unittest
import StringIO
from djangopypi.views import parse_distutils_request
+from djangopypi.models import Project, Classifier
+from django.test.client import Client
+from django.core.urlresolvers import reverse
+from django.contrib.auth.models import User
def create_post_data(action):
data = {
@@ -80,11 +84,27 @@ def test_weird_post_data(self):
data = create_post_data("submit")
raw_post_data = create_request(data)
post, files = parse_distutils_request(MockRequest(raw_post_data))
- print("post: %s files: %s" % (post, files))
self.assertTrue(post)
for key in post.keys():
if isinstance(data[key], list):
self.assertEquals(data[key], post.getlist(key))
+ elif data[key] == "UNKNOWN":
+ self.assertTrue(post[key] is None)
else:
self.assertEquals(post[key], data[key])
+
+class TestSearch(unittest.TestCase):
+
+ def setUp(self):
+ data = create_post_data("submit")
+ dummy_user = User.objects.create(username='krill', password='12345',
+ email='krill@opera.com')
+ Project.objects.create(name=data['name'], license=data['license'],
+ summary=data["summary"], owner=dummy_user)
+
+
+ def testSearchForPackage(self):
+ client = Client()
+ response = client.post(reverse('djangopypi-search'), {'search_term': 'foo'})
+ self.assertTrue("The quick brown fox jumps over the lazy dog." in response.content)
View
5 djangopypi/urls.py
@@ -19,5 +19,6 @@
url(r'^(?P<dist_name>[\w\d_\.\-]+)/$', "show_links",
{'template_name': 'djangopypi/pypi_show_links.html'},
name="djangopypi-pypi_show_links"),
-)
-
+
+ url(r'^search','search',name='djangopypi-search')
+)
View
229 djangopypi/views.py
@@ -1,229 +0,0 @@
-import cgi
-import os
-
-try:
- from cStringIO import StringIO
-except ImportError:
- from StringIO import StringIO
-
-from django.conf import settings
-from django.http import Http404, HttpResponse, HttpResponseBadRequest
-from django.http import QueryDict, HttpResponseForbidden
-from django.shortcuts import render_to_response
-from django.template import RequestContext
-from django.utils.datastructures import MultiValueDict
-from django.utils.translation import ugettext_lazy as _
-from django.core.files.uploadedfile import SimpleUploadedFile
-from django.contrib.auth import authenticate, login
-
-from registration.backends import get_backend
-from registration.forms import RegistrationForm
-
-from djangopypi.models import Project, Classifier, Release, UPLOAD_TO
-from djangopypi.forms import ProjectForm, ReleaseForm
-from djangopypi.http import HttpResponseUnauthorized
-from djangopypi.http import HttpResponseNotImplemented
-from djangopypi.utils import decode_fs
-
-
-ALREADY_EXISTS_FMT = _("""A file named "%s" already exists for %s. To fix """
- + "problems with that you should create a new release.")
-
-
-def parse_distutils_request(request):
- raw_post_data = request.raw_post_data
- sep = raw_post_data.splitlines()[1]
- items = raw_post_data.split(sep)
- post_data = {}
- files = {}
- for part in filter(lambda e: not e.isspace(), items):
- item = part.splitlines()
- if len(item) < 2:
- continue
- header = item[1].replace("Content-Disposition: form-data; ", "")
- kvpairs = header.split(";")
- headers = {}
- for kvpair in kvpairs:
- if not kvpair:
- continue
- key, value = kvpair.split("=")
- headers[key] = value.strip('"')
- if "name" not in headers:
- continue
- content = part[len("\n".join(item[0:2]))+2:len(part)-1]
- if "filename" in headers:
- file = SimpleUploadedFile(headers["filename"], content,
- content_type="application/gzip")
- files["distribution"] = [file]
- elif headers["name"] in post_data:
- post_data[headers["name"]].append(content)
- else:
- # Distutils sends UNKNOWN for empty fields (e.g platform)
- # [russell.sim@gmail.com]
- if content == 'UNKNOWN':
- post_data[headers["name"]] = [None]
- else:
- post_data[headers["name"]] = [content]
-
- return MultiValueDict(post_data), MultiValueDict(files)
-
-
-
-def login_basic_auth(request):
- authentication = request.META.get("HTTP_AUTHORIZATION")
- if not authentication:
- return
- (authmeth, auth) = authentication.split(' ', 1)
- if authmeth.lower() != "basic":
- return
- auth = auth.strip().decode("base64")
- username, password = auth.split(":", 1)
- return authenticate(username=username, password=password)
-
-
-def submit_project_or_release(user, post_data, files):
- """Registers/updates a project or release"""
- try:
- project = Project.objects.get(name=post_data['name'])
- if project.owner != user:
- return HttpResponseForbidden(
- "That project is owned by someone else!")
- except Project.DoesNotExist:
- project = None
-
- project_form = ProjectForm(post_data, instance=project)
- if project_form.is_valid():
- project = project_form.save(commit=False)
- project.owner = user
- project.save()
- for c in post_data.getlist('classifiers'):
- classifier, created = Classifier.objects.get_or_create(name=c)
- project.classifiers.add(classifier)
- if files:
- allow_overwrite = getattr(settings,
- "DJANGOPYPI_ALLOW_VERSION_OVERWRITE", False)
- try:
- release = Release.objects.get(version=post_data['version'],
- project=project,
- distribution=UPLOAD_TO + '/' +
- files['distribution']._name)
- if not allow_overwrite:
- return HttpResponseForbidden(ALREADY_EXISTS_FMT % (
- release.filename, release))
- except Release.DoesNotExist:
- release = None
-
- # If the old file already exists, django will append a _ after the
- # filename, however with .tar.gz files django does the "wrong"
- # thing and saves it as project-0.1.2.tar_.gz. So remove it before
- # django sees anything.
- release_form = ReleaseForm(post_data, files, instance=release)
- if release_form.is_valid():
- if release and os.path.exists(release.distribution.path):
- os.remove(release.distribution.path)
- release = release_form.save(commit=False)
- release.project = project
- release.save()
- else:
- return HttpResponseBadRequest(
- "ERRORS: %s" % release_form.errors)
- else:
- return HttpResponseBadRequest("ERRORS: %s" % project_form.errors)
-
- return HttpResponse()
-
-
-def register_or_upload(request, post_data, files):
- user = login_basic_auth(request)
- if not user:
- return HttpResponseUnauthorized('pypi')
-
- login(request, user)
- if not request.user.is_authenticated():
- return HttpResponseForbidden(
- "Not logged in, or invalid username/password.")
-
- return submit_project_or_release(user, post_data, files)
-
-def create_user(request, post_data, files):
- """Create new user from a distutil client request"""
- form = RegistrationForm({"username": post_data["name"],
- "email": post_data["email"],
- "password1": post_data["password"],
- "password2": post_data["password"]})
- if not form.is_valid():
- # Dist Utils requires error msg in HTTP status: "HTTP/1.1 400 msg"
- # Which is HTTP/WSGI incompatible, so we're just returning a empty 400.
- return HttpResponseBadRequest()
-
- backend = get_backend("registration.backends.default.DefaultBackend")
- if not backend.registration_allowed(request):
- return HttpResponseBadRequest()
- new_user = backend.register(request, **form.cleaned_data)
- return HttpResponse("OK\n", status=200, mimetype='text/plain')
-
-
-ACTIONS = {
- # file_upload is the action used with distutils ``sdist`` command.
- "file_upload": register_or_upload,
-
- # submit is the :action used with distutils ``register`` command.
- "submit": register_or_upload,
-
- # user is the action used when registering a new user
- "user": create_user,
-}
-
-
-def simple(request, template_name="djangopypi/simple.html"):
- if request.method == "POST":
- post_data, files = parse_distutils_request(request)
- action_name = post_data.get(":action")
- if action_name not in ACTIONS:
- return HttpResponseNotImplemented(
- "The action %s is not implemented" % action_name)
- return ACTIONS[action_name](request, post_data, files)
-
- dists = Project.objects.all().order_by("name")
- context = RequestContext(request, {
- "dists": dists,
- "title": 'Package Index',
- })
-
- return render_to_response(template_name, context_instance=context)
-
-
-def show_links(request, dist_name,
- template_name="djangopypi/show_links.html"):
- try:
- project = Project.objects.get(name=dist_name)
- releases = project.releases.all().order_by('-version')
- except Project.DoesNotExist:
- raise Http404
-
- context = RequestContext(request, {
- "dist_name": dist_name,
- "releases": releases,
- "project": project,
- "title": project.name,
- })
-
- return render_to_response(template_name, context_instance=context)
-
-
-def show_version(request, dist_name, version,
- template_name="djangopypi/show_version.html"):
- try:
- release = Project.objects.get(name=dist_name).releases \
- .get(version=version)
- except Project.DoesNotExist:
- raise Http404()
-
- context = RequestContext(request, {
- "dist_name": dist_name,
- "version": version,
- "release": release,
- "title": dist_name,
- })
-
- return render_to_response(template_name, context_instance=context)
View
76 djangopypi/views/__init__.py
@@ -0,0 +1,76 @@
+from django.http import Http404
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+
+from djangopypi.models import Project, Release
+from djangopypi.http import HttpResponseNotImplemented
+from djangopypi.http import parse_distutils_request
+from djangopypi.views.dists import register_or_upload
+from djangopypi.views.users import create_user
+from djangopypi.views.search import search
+
+
+ACTIONS = {
+ # file_upload is the action used with distutils ``sdist`` command.
+ "file_upload": register_or_upload,
+
+ # submit is the :action used with distutils ``register`` command.
+ "submit": register_or_upload,
+
+ # user is the action used when registering a new user
+ "user": create_user,
+}
+
+
+def simple(request, template_name="djangopypi/simple.html"):
+ if request.method == "POST":
+ post_data, files = parse_distutils_request(request)
+ action_name = post_data.get(":action")
+ if action_name not in ACTIONS:
+ return HttpResponseNotImplemented(
+ "The action %s is not implemented" % action_name)
+ return ACTIONS[action_name](request, post_data, files)
+
+ dists = Project.objects.all().order_by("name")
+ context = RequestContext(request, {
+ "dists": dists,
+ "title": 'Package Index',
+ })
+
+ return render_to_response(template_name, context_instance=context)
+
+
+def show_links(request, dist_name,
+ template_name="djangopypi/show_links.html"):
+ try:
+ project = Project.objects.get(name=dist_name)
+ releases = project.releases.all().order_by('-version')
+ except Project.DoesNotExist:
+ raise Http404
+
+ context = RequestContext(request, {
+ "dist_name": dist_name,
+ "releases": releases,
+ "project": project,
+ "title": project.name,
+ })
+
+ return render_to_response(template_name, context_instance=context)
+
+
+def show_version(request, dist_name, version,
+ template_name="djangopypi/show_version.html"):
+ try:
+ project = Project.objects.get(name=dist_name)
+ release = project.releases.get(version=version)
+ except (Project.DoesNotExist, Release.DoesNotExist):
+ raise Http404()
+
+ context = RequestContext(request, {
+ "dist_name": dist_name,
+ "version": version,
+ "release": release,
+ "title": dist_name,
+ })
+
+ return render_to_response(template_name, context_instance=context)
View
79 djangopypi/views/dists.py
@@ -0,0 +1,79 @@
+import os
+
+from django.conf import settings
+from django.http import (HttpResponse, HttpResponseForbidden,
+ HttpResponseBadRequest)
+from django.utils.translation import ugettext_lazy as _
+from django.contrib.auth import login
+
+from djangopypi.http import login_basic_auth, HttpResponseUnauthorized
+from djangopypi.forms import ProjectForm, ReleaseForm
+from djangopypi.models import Project, Release, Classifier, UPLOAD_TO
+
+ALREADY_EXISTS_FMT = _(
+ "A file named '%s' already exists for %s. Please create a new release.")
+
+
+def submit_project_or_release(user, post_data, files):
+ """Registers/updates a project or release"""
+ try:
+ project = Project.objects.get(name=post_data['name'])
+ if project.owner != user:
+ return HttpResponseForbidden(
+ "That project is owned by someone else!")
+ except Project.DoesNotExist:
+ project = None
+
+ project_form = ProjectForm(post_data, instance=project)
+ if project_form.is_valid():
+ project = project_form.save(commit=False)
+ project.owner = user
+ project.save()
+ for c in post_data.getlist('classifiers'):
+ classifier, created = Classifier.objects.get_or_create(name=c)
+ project.classifiers.add(classifier)
+ if files:
+ allow_overwrite = getattr(settings,
+ "DJANGOPYPI_ALLOW_VERSION_OVERWRITE", False)
+ try:
+ release = Release.objects.get(version=post_data['version'],
+ project=project,
+ distribution=UPLOAD_TO + '/' +
+ files['distribution']._name)
+ if not allow_overwrite:
+ return HttpResponseForbidden(ALREADY_EXISTS_FMT % (
+ release.filename, release))
+ except Release.DoesNotExist:
+ release = None
+
+ # If the old file already exists, django will append a _ after the
+ # filename, however with .tar.gz files django does the "wrong"
+ # thing and saves it as project-0.1.2.tar_.gz. So remove it before
+ # django sees anything.
+ release_form = ReleaseForm(post_data, files, instance=release)
+ if release_form.is_valid():
+ if release and os.path.exists(release.distribution.path):
+ os.remove(release.distribution.path)
+ release = release_form.save(commit=False)
+ release.project = project
+ release.save()
+ else:
+ return HttpResponseBadRequest(
+ "ERRORS: %s" % release_form.errors)
+ else:
+ return HttpResponseBadRequest("ERRORS: %s" % project_form.errors)
+
+ return HttpResponse()
+
+
+def register_or_upload(request, post_data, files):
+ user = login_basic_auth(request)
+ if not user:
+ return HttpResponseUnauthorized('pypi')
+
+ login(request, user)
+ if not request.user.is_authenticated():
+ return HttpResponseForbidden(
+ "Not logged in, or invalid username/password.")
+
+ return submit_project_or_release(user, post_data, files)
View
24 djangopypi/views/search.py
@@ -0,0 +1,24 @@
+from django.template import RequestContext
+from django.shortcuts import render_to_response
+from django.db.models.query import Q
+
+from djangopypi.models import Project
+
+
+def _search_query(q):
+ return Q(name__contains=q) | Q(summary__contains=q)
+
+
+def search(request, template="djangopypi/search_results.html"):
+ context = RequestContext(request, {"dists": None, "search_term": ""})
+
+ if request.method == "POST":
+ search_term = context["search_term"] = request.POST.get("search_term")
+ if search_term:
+ query = _search_query(search_term)
+ context["dists"] = Project.objects.filter(query)
+
+ if context["dists"] is None:
+ context["dists"] = Project.objects.all()
+
+ return render_to_response(template, context_instance=context)
View
24 djangopypi/views/users.py
@@ -0,0 +1,24 @@
+from django.http import HttpResponse, HttpResponseBadRequest
+
+from registration.forms import RegistrationForm
+from registration.backends import get_backend
+
+DEFAULT_BACKEND = "registration.backends.default.DefaultBackend"
+
+
+def create_user(request, post_data, files, backend_name=DEFAULT_BACKEND):
+ """Create new user from a distutil client request"""
+ form = RegistrationForm({"username": post_data["name"],
+ "email": post_data["email"],
+ "password1": post_data["password"],
+ "password2": post_data["password"]})
+ if not form.is_valid():
+ # Dist Utils requires error msg in HTTP status: "HTTP/1.1 400 msg"
+ # Which is HTTP/WSGI incompatible, so we're just returning a empty 400.
+ return HttpResponseBadRequest()
+
+ backend = get_backend(backend_name)
+ if not backend.registration_allowed(request):
+ return HttpResponseBadRequest()
+ new_user = backend.register(request, **form.cleaned_data)
+ return HttpResponse("OK\n", status=200, mimetype='text/plain')
Please sign in to comment.
Something went wrong with that request. Please try again.