diff --git a/.gitignore b/.gitignore index ec50a6b..4ca0a28 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ local_config.py vilya.log .vagrant/ code.local.env +db.sqlite3 diff --git a/app.py b/app.py index 81e9eb6..e93672e 100644 --- a/app.py +++ b/app.py @@ -4,10 +4,47 @@ from gevent import monkey; monkey.patch_all() from web import app as web from app_sina import app as git_http +from vilya.wsgi import application as django_app ROUTE_MAP = [(re.compile(r'/[^/]*\.git.*'), git_http), (re.compile(r'/[^/]*/([^/]*)\.git.*'), git_http), + (re.compile(r'/admin'), django_app), + (re.compile(r'/people'), django_app), + (re.compile(r'/gist'), django_app), + (re.compile(r'/register'), django_app), + (re.compile(r'/login'), django_app), + (re.compile(r'/logout'), django_app), + (re.compile(r'/mirrors'), django_app), + (re.compile(r'/badge'), django_app), + (re.compile(r'/watch'), django_app), + (re.compile(r'/watching'), django_app), + (re.compile(r'/favorites'), django_app), + (re.compile(r'/m'), django_app), + (re.compile(r'/praise'), django_app), + (re.compile(r'/trello'), django_app), + (re.compile(r'/settings'), django_app), + #(re.compile(r'/\w+/\w+/?$'), django_app), + (re.compile(r'/\w+/\w+/watchers'), django_app), + (re.compile(r'/\w+/\w+/forkers'), django_app), + (re.compile(r'/\w+/\w+/archive'), django_app), + (re.compile(r'/\w+/\w+/settings'), django_app), + (re.compile(r'/\w+/\w+/blob'), django_app), + (re.compile(r'/\w+/\w+/edit'), django_app), + (re.compile(r'/\w+/\w+/tree'), django_app), + (re.compile(r'/\w+/\w+/commits'), django_app), + (re.compile(r'/\w+/\w+/blame'), django_app), + (re.compile(r'/\w+/\w+/raw'), django_app), + (re.compile(r'/\w+/\w+/browsefiles'), django_app), + (re.compile(r'/\w+/\w+/code_review'), django_app), + (re.compile(r'/\w+/\w+/comments'), django_app), + (re.compile(r'/\w+/\w+/compare'), django_app), + (re.compile(r'/\w+/\w+/issues'), django_app), + (re.compile(r'/\w+/\w+/issue_comments'), django_app), + (re.compile(r'/j/'), django_app), + (re.compile(r'/hub/'), django_app), + (re.compile(r'/teams/'), django_app), + (re.compile(r'/vilya'), django_app), (re.compile(r'/.*'), web)] diff --git a/dockerfiles/python/requirements.txt b/dockerfiles/python/requirements.txt index 21953a8..e92b5d2 100644 --- a/dockerfiles/python/requirements.txt +++ b/dockerfiles/python/requirements.txt @@ -35,3 +35,4 @@ docutils==0.12 kombu==3.0.33 billiard==3.3.0.22 paramiko==1.16.0 +Django==1.9.2 diff --git a/favicon.ico b/hub/static/favicon.ico similarity index 100% rename from favicon.ico rename to hub/static/favicon.ico diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..4187c86 --- /dev/null +++ b/manage.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "vilya.settings") + + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) diff --git a/tests/test_project.py b/tests/test_project.py index 90bde28..4724527 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -10,7 +10,7 @@ from vilya.config import DOMAIN from vilya.models.project import CodeDoubanProject -from vilya.libs.text import gravatar_url +from vilya.libs.gravatar import gravatar_url from vilya.libs.permdir import get_repo_root diff --git a/vilya/admin.py b/vilya/admin.py new file mode 100644 index 0000000..ef4e8d7 --- /dev/null +++ b/vilya/admin.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from django.contrib import admin +from django.contrib.auth.admin import UserAdmin as BaseUserAdmin +from django.contrib.auth.models import User +from vilya.models.django.user import UserProfile + + +class UserProfileInline(admin.StackedInline): + model = UserProfile + can_delete = False + verbose_name_plural = 'profile' + + +class UserAdmin(BaseUserAdmin): + inlines = (UserProfileInline, ) + + +admin.site.unregister(User) +admin.site.register(User, UserAdmin) diff --git a/vilya/apps.py b/vilya/apps.py new file mode 100644 index 0000000..cd9e186 --- /dev/null +++ b/vilya/apps.py @@ -0,0 +1,7 @@ +from __future__ import unicode_literals + +from django.apps import AppConfig + + +class VilyaConfig(AppConfig): + name = 'vilya' diff --git a/vilya/libs/emoji.py b/vilya/libs/emoji.py index 18825e5..d9c5caa 100644 --- a/vilya/libs/emoji.py +++ b/vilya/libs/emoji.py @@ -12,7 +12,7 @@ ':kissing_smiling_eyes:', ':tropical_drink:', ':face_with_medical_mask:', ':pill:', ':ruby:', ':cactus:', ':smiley_stuck_out_tongue_winking_eye:', ':boar:', ':smile:', ':face_with_tear_of_joy:', ':Cancer:', - ':couple_in_love:', ':horse:', ':two_men_with_heart:', ':bowtie:', + ':couple_in_love:', ':horse:', ':two_men_with_heart:', ':open_mouth:', ':frog_face:', ':Taurus:', ':octopus:', ':ship:', ':shooting_star:', ':face_with_ok_gesture:', ':wolf_face:', ':heart:', ':loudly_crying_face:', ':frowning:', ':scuba_diver:', ':love_hotel:', @@ -23,7 +23,7 @@ ':tennis_racquet_and_ball:', ':person_frowning:', ':spouting_whale:', ':tangerine:', ':person_bowing_deeply:', ':stuck_out_tongue_closed_eyes:', ':dog_face:', ':circled_ideograph_secret:', ':Libra:', ':jumping_spider:', - ':disappointed_face:', ':hamburger:', ':octocat:', ':sleeping:', + ':disappointed_face:', ':hamburger:', ':sleeping:', ':crescent_moon:', ':no_one_under_eighteen_symbol:', ':kissing:', ':unamused:', ':couple_with_heart:', ':fisted_hand_sign:', ':smiling_cat_face_with_heart_shaped_eyes:', ':anguished:', ':groupme:', @@ -103,7 +103,13 @@ ':1f681:', ':1f682:' ] -EMOJI_GROUPS = {} +EMOJI_GROUPS = { + ":mergetime:": """ +:zap::zap::zap::zap::zap::zap::zap::zap::zap::zap: +:zap::metal: M E R G E T I M E :metal::zap: +:zap::zap::zap::zap::zap::zap::zap::zap::zap::zap: +""", +} def parse_emoji_groups(text): diff --git a/vilya/libs/git2.py b/vilya/libs/git2.py index c9d9309..4d730e5 100644 --- a/vilya/libs/git2.py +++ b/vilya/libs/git2.py @@ -138,13 +138,13 @@ def rev_list(self, to_ref, from_ref=None, to_commit = self.revparse_single(to_ref) if to_commit.type == GIT_OBJ_TAG: to_commit = self[to_commit.target] - walker = self.walk(to_commit.id, GIT_SORT_TIME) + walker = self.walk(to_commit.oid, GIT_SORT_TIME) if from_ref: try: from_commit = self.revparse_single(from_ref) if from_commit.type == GIT_OBJ_TAG: from_commit = self[from_commit.target] - walker.hide(from_commit.id) + walker.hide(from_commit.oid) except KeyError: from_commit = None @@ -411,7 +411,7 @@ def ls_tree(self, ref, recursive=None, size=None, name_only=None): if size: if objtype == 'blob': - blob = self[entry.id] + blob = self[entry.oid] item = [mode, objtype, entry.hex, blob.size, path] else: item = [mode, objtype, entry.hex, '-', path] diff --git a/vilya/libs/gravatar.py b/vilya/libs/gravatar.py new file mode 100644 index 0000000..8142104 --- /dev/null +++ b/vilya/libs/gravatar.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +import urllib +import hashlib + + +def gravatar_url(email, size=80): + # 线上尺寸图已有size: (48, 64, 80) + default = "http://www.gravatar.com/avatar" + url = "http://www.gravatar.com/avatar/" + hashlib.md5( + email.encode('utf8').lower()).hexdigest() + "?" + url += urllib.urlencode({'d': default, 's': str(size), 'r': 'x'}) + return url diff --git a/vilya/libs/middleware.py b/vilya/libs/middleware.py new file mode 100644 index 0000000..c945f92 --- /dev/null +++ b/vilya/libs/middleware.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- + + +# TODO(xutao) remove quixote middleware +# FIXME(xutao) remove compatibility for quixote + +class QuixoteMiddleware(object): + + def process_request(self, request): + from vilya.models.user import User + # FIXME(xutao) remove get_header + if not hasattr(request, 'get_header'): + def get_header(name): + return request.META.get(name) + request.get_header = get_header + + if not hasattr(request, 'get_path'): + def get_path(n=0): + path_info = request.environ.get('PATH_INFO', '') + path = request.environ['SCRIPT_NAME'] + path_info + if n == 0: + return path + else: + path_comps = path.split('/') + if abs(n) > len(path_comps)-1: + raise ValueError, "n=%d too big for path '%s'" % (n, path) + if n > 0: + return '/'.join(path_comps[:-n]) + elif n < 0: + return '/'.join(path_comps[:-n+1]) + else: + assert 0, "Unexpected value for n (%s)" % n + request.get_path = get_path + + if not hasattr(request, 'is_mobile'): + from vilya.views.util import is_mobile_device + request.is_mobile = is_mobile_device(request) + + if not hasattr(request, 'url'): + request.url = request.get_path() + + # translate django user to quixote user + user = request.user + request.django_user = user + request.user = User(user.username) diff --git a/vilya/libs/rdstore.py b/vilya/libs/rdstore.py index 25489b5..0a2f495 100644 --- a/vilya/libs/rdstore.py +++ b/vilya/libs/rdstore.py @@ -8,5 +8,4 @@ def init_store(): return redis.from_url(REDIS_URI) -rdstore = init_store() -rds = rdstore +rds = init_store() diff --git a/vilya/libs/text.py b/vilya/libs/text.py index 12619a9..9bceb05 100644 --- a/vilya/libs/text.py +++ b/vilya/libs/text.py @@ -1,10 +1,9 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import import re -import urllib -import hashlib import docutils import docutils.core +from vilya.libs.gravatar import gravatar_url # noqa from mikoto.libs.text import * # noqa from mikoto.libs.emoji import * # noqa @@ -74,15 +73,6 @@ def is_binary(fname): return False -def gravatar_url(email, size=80): - # 线上尺寸图已有size: (48, 64, 80) - default = "http://www.gravatar.com/avatar" - url = "http://www.gravatar.com/avatar/" + hashlib.md5( - email.encode('utf8').lower()).hexdigest() + "?" - url += urllib.urlencode({'d': default, 's': str(size), 'r': 'x'}) - return url - - def remove_unknown_character(text): if isinstance(text, str): return text.decode('utf-8', 'ignore').encode('utf-8', 'ignore') diff --git a/vilya/migrations/__init__.py b/vilya/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/vilya/models/django/__init__.py b/vilya/models/django/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/vilya/models/django/project.py b/vilya/models/django/project.py new file mode 100644 index 0000000..b7d4054 --- /dev/null +++ b/vilya/models/django/project.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +from django.db import models +from django.contrib.auth.models import User + + +class Project(models.Model): + + # TODO(xutao) name regex + name = models.CharField(max_length=100) + user = models.OneToOneField(User, on_delete=models.CASCADE) + desc = models.CharField(max_length=255) + path = models.CharField(max_length=100) + full_name = models.CharField(max_length=70) + created_at = models.DateTimeField('created at', auto_now_add=True) + updated_at = models.DateTimeField('updated at', auto_now=True) + + def __str__(self): + return self.full_name + + def __hash__(self): + return hash(self.id) + + def __eq__(self, other): + return isinstance(other, Project) and self.id == other.id + + def __ne__(self, other): + return not self.__eq__(other) diff --git a/vilya/models/django/user.py b/vilya/models/django/user.py new file mode 100644 index 0000000..085bc9d --- /dev/null +++ b/vilya/models/django/user.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models +from django.contrib.auth.models import User +from vilya.libs.gravatar import gravatar_url +from vilya.models.inbox import Inbox + + +class UserProfile(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE) + + # TODO(xutao) gravatar + # TODO(xutao) trello token + + # TODO(xutao) team + # TODO(xutao) project + # TODO(xutao) notification + # TODO(xutao) badge + # TODO(xutao) setting + + def get_gravatar_url(self, size=80): + return gravatar_url(self.email, size) + + @property + def inbox(self): + return Inbox.get(user=self.name) + + @property + def unread_notification_count(self): + from vilya.models.notification import Notification + return Notification.unread_count(self.name) + + +class UserFollower(models.Model): + follower = models.ForeignKey(User, db_constraint=False, related_name='follower') + followee = models.ForeignKey(User, db_constraint=False, related_name='followee') + created_at = models.DateTimeField('created at', auto_now_add=True) + updated_at = models.DateTimeField('updated at', auto_now=True) + + +class UserEmail(models.Model): + user = models.ForeignKey(User, db_constraint=False) + email = models.CharField(max_length=100) + created_at = models.DateTimeField('created at', auto_now_add=True) + updated_at = models.DateTimeField('updated at', auto_now=True) + + +class UserKey(models.Model): + title = models.CharField(max_length=128) + text = models.CharField(max_length=1024) + fingerprint = models.CharField(max_length=48) + user = models.ForeignKey(User, db_constraint=False) + created_at = models.DateTimeField('created at', auto_now_add=True) + updated_at = models.DateTimeField('updated at', auto_now=True) + + +# TODO(xutao) support kind for activity? +class Badge(models.Model): + desc = models.CharField(max_length=1024) + created_at = models.DateTimeField('created at', auto_now_add=True) + updated_at = models.DateTimeField('updated at', auto_now=True) + + +class UserBadge(models.Model): + user = models.ForeignKey(User, db_constraint=False) + badge = models.ForeignKey(Badge, db_constraint=False) + desc = models.CharField(max_length=1024) + created_at = models.DateTimeField('created at', auto_now_add=True) + updated_at = models.DateTimeField('updated at', auto_now=True) diff --git a/vilya/models/gist.py b/vilya/models/gist.py index 3a4ba75..191e9de 100644 --- a/vilya/models/gist.py +++ b/vilya/models/gist.py @@ -108,12 +108,17 @@ def git_path(self): @property def git_url(self): + # TODO(xutao) user namespace return '%s/%s' % (DOMAIN, self.git_path) @property def url(self): return '%s/gist/%s/%s' % (DOMAIN, self.owner_id, self.id) + @property + def relative_url(self): + return '/gist/%s/%s' % (self.owner_id, self.id) + @property def download_url(self): return self.url + '/download' @@ -324,7 +329,7 @@ def forks_by_user(cls, owner_id, start=0, limit=10, sort='created', return [cls(*r) for r in rs] @classmethod - def stars_by_user(cls, owner_id, start=0, limit=10): + def stars_by_user(cls, owner_id, start=0, limit=10, sort=None, direction=None): rs = store.execute( "select gist_id from gist_stars " "where user_id=%s order by id desc limit %s, %s", diff --git a/vilya/models/user.py b/vilya/models/user.py index 51ec8c3..b996860 100644 --- a/vilya/models/user.py +++ b/vilya/models/user.py @@ -9,7 +9,7 @@ from quixote import get_user as get_current_user_id, get_session from vilya.libs.store import store, cache, mc, ONE_MONTH -from vilya.libs.text import gravatar_url +from vilya.libs.gravatar import gravatar_url from vilya.libs.props import PropsMixin, PropsItem from vilya.libs.validators import check_email, check_name from vilya.libs.signals import follow_user_signal diff --git a/vilya/settings.py b/vilya/settings.py new file mode 100644 index 0000000..770ced7 --- /dev/null +++ b/vilya/settings.py @@ -0,0 +1,133 @@ +""" +Django settings for vilya project. + +Generated by 'django-admin startproject' using Django 1.9.2. + +For more information on this file, see +https://docs.djangoproject.com/en/1.9/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.9/ref/settings/ +""" + +import os + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'grh6lqc6)7%5cmw1r4k04(o)mv*molg_^h-0nn8wxk6%7igc6@' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'vilya' +] + +MIDDLEWARE_CLASSES = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + # FIXME(xutao) remove QuixoteMiddleware + 'vilya.libs.middleware.QuixoteMiddleware', +] + +ROOT_URLCONF = 'vilya.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'vilya.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/1.9/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.mysql', + 'NAME': 'valentine', + 'USER': 'root', + 'PASSWORD': '', + 'HOST': 'localhost', + 'PORT': '3306' + } +} + + +# Password validation +# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/1.9/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.9/howto/static-files/ + +STATIC_URL = '/static/' + +try: + from local_config import * # noqa +except ImportError: + pass diff --git a/vilya/templates/base.html b/vilya/templates/base.html index 0a22c2c..a275a1a 100644 --- a/vilya/templates/base.html +++ b/vilya/templates/base.html @@ -22,7 +22,6 @@ ${ make_title() } ${self.head_style()} ${self.extra_head()} - ${self.raven_js()} @@ -171,7 +170,6 @@ ${self.bottom_script()} - ${self.ga()} @@ -305,23 +303,6 @@ <%def name="bottom_script()"> -<%def name="ga()"> - - - <%def name="head_style()"> @@ -329,9 +310,3 @@ <%def name="extra_head()"> - -<%def name="raven_js()"> - % if not DEVELOP_MODE: - - % endif - diff --git a/vilya/templates/blame.html b/vilya/templates/blame.html index ceb690e..0d52924 100644 --- a/vilya/templates/blame.html +++ b/vilya/templates/blame.html @@ -10,7 +10,7 @@ ${parent.body()} <%! - from vilya.libs.text import gravatar_url + from vilya.libs.gravatar import gravatar_url from urllib import urlencode %> diff --git a/vilya/templates/future.html b/vilya/templates/future.html index b459efd..f509a65 100644 --- a/vilya/templates/future.html +++ b/vilya/templates/future.html @@ -11,5 +11,5 @@

面包会有的,功能也会有的 :)

-

你也可以到 http://code.dapps.douban.com/code 提建议和意见

+

你也可以到 https://github.com/douban/code 提建议和意见

diff --git a/vilya/templates/gist/gist_base.html b/vilya/templates/gist/gist_base.html index f81a230..926f0be 100644 --- a/vilya/templates/gist/gist_base.html +++ b/vilya/templates/gist/gist_base.html @@ -114,7 +114,7 @@

  • - +
  • @@ -179,21 +179,21 @@

    ${path}

    <% link_path = request.get_path() %> -
  • Gist Detail
  • -
  • Gist Detail
  • +
  • Revisions ${ gist.n_revision }
  • % if gist.n_fork: -
  • Forks ${ gist.n_fork }
  • % endif % if gist.n_star: -
  • Stars ${ gist.n_star } diff --git a/vilya/templates/page/promo_proj.html b/vilya/templates/page/promo_proj.html deleted file mode 100644 index 87df206..0000000 --- a/vilya/templates/page/promo_proj.html +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - -
    ${promo_proj['title']}
    -
    - - - -
    -
    -
    - ${promo_proj['description']|n} -
    - - diff --git a/vilya/templates/people.html b/vilya/templates/people.html index 43615d2..0c509bf 100644 --- a/vilya/templates/people.html +++ b/vilya/templates/people.html @@ -94,7 +94,6 @@

    Teams

    % endif
    -
    diff --git a/vilya/templates/tree.html b/vilya/templates/tree.html index 71e6f44..aa359ff 100644 --- a/vilya/templates/tree.html +++ b/vilya/templates/tree.html @@ -3,9 +3,6 @@ <%namespace name="pjax" file="/widgets/pjax_source.html" /> <%def name="extra_head()"> -<% -is_go_get = request.get_form_var('go-get', 0) -%> %if is_go_get: %endif diff --git a/vilya/templates/widgets/milestones.html b/vilya/templates/widgets/milestones.html index 4b9b569..ce7750a 100644 --- a/vilya/templates/widgets/milestones.html +++ b/vilya/templates/widgets/milestones.html @@ -3,7 +3,11 @@ <% import copy import urllib - form = copy.deepcopy(request.form) + # FIXME(xutao) move this to views + if hasattr(request, 'POST'): + form = copy.deepcopy(request.POST) + else: + form = copy.deepcopy(request.form) %>
    diff --git a/vilya/templates/widgets/side_list.html b/vilya/templates/widgets/side_list.html index 18d2f6d..b7ec718 100644 --- a/vilya/templates/widgets/side_list.html +++ b/vilya/templates/widgets/side_list.html @@ -111,7 +111,6 @@
    -
    %if len(watched_projects) > 0:
    diff --git a/vilya/templates/widgets/tags.html b/vilya/templates/widgets/tags.html index 7d8ef2b..a3bcae0 100644 --- a/vilya/templates/widgets/tags.html +++ b/vilya/templates/widgets/tags.html @@ -2,7 +2,11 @@ <% import copy import urllib - form = copy.deepcopy(request.form) + # FIXME(xutao) move this to views + if hasattr(request, 'POST'): + form = copy.deepcopy(request.POST) + else: + form = copy.deepcopy(request.form) tags = form.get('tags', '') selected_tag_names = tags.split(',') if tags else [] if key_word: diff --git a/vilya/tests.py b/vilya/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/vilya/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/vilya/urls.py b/vilya/urls.py new file mode 100644 index 0000000..40149c2 --- /dev/null +++ b/vilya/urls.py @@ -0,0 +1,222 @@ +"""vilya URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/1.9/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.conf.urls import url, include + 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) +""" +from django.conf.urls import url +from django.contrib import admin +from django.views.generic.base import RedirectView +from views.django import views +from views.django import user +from views.django import team +from views.django import team_issue +from views.django import gist +from views.django import gist_user +from views.django import gist_raw +from views.django import gist_embed +from views.django import gist_comment +from views.django import badge +from views.django import project +from views.django import project_setting +from views.django import project_issue +from views.django import trello +from views.django import setting +from views.django import hub +from views.django import hub_team + + +urlpatterns = [ + # user + url(r'^login/?$', user.login, name='user_login'), + url(r'^logout/?$', user.logout, name='user_logout'), + url(r'^register/?$', user.register, name='user_register'), + url(r'^people/(?P\w+)/praises', user.praises, name='user_praises'), + url(r'^people/(?P\w+)/followers', user.followers, name='user_followers'), + url(r'^people/(?P\w+)/following', user.following, name='user_following'), + url(r'^people/(?P\w+)/badges', user.badges, name='user_badges'), + url(r'^people/(?P\w+)/contributions', user.contributions, name='user_contributions'), + url(r'^people/(?P\w+)/contribution_detail', user.contribution_detail, name='user_contribution_detail'), + url(r'^people/(?P\w+)', user.index, name='user_index'), + url(r'^watching/?$', user.watching, name="user_watching"), + url(r'^favorites/?$', user.favorites, name="user_favorites"), + url(r'^praise/?$', user.praise_index, name="user_praise_index"), + url(r'^praise/vote/?$', user.praise_vote, name="user_praise_vote"), + url(r'^trello/bind/?$', trello.bind, name="trello_bind"), + url(r'^trello/unbind/?$', trello.unbind, name="trello_unbind"), + url(r'^settings/?$', setting.index, name='setting_index'), + url(r'^settings/emails$', RedirectView.as_view(pattern_name='setting_emails')), + url(r'^settings/emails/$', setting.emails, name='setting_emails'), + url(r'^settings/emails/(?P[0-9]+)/delete/?$', setting.emails_delete, name='setting_emails_delete'), + url(r'^settings/emails/(?P[0-9]+)/set_notif/?$', setting.emails_set_notif, name='setting_emails_set_notif'), + url(r'^settings/emails/(?P[0-9]+)/un_notif/?$', setting.emails_un_notif, name='setting_emails_un_notif'), + url(r'^settings/github$', RedirectView.as_view(pattern_name='setting_github')), + url(r'^settings/github/$', setting.github, name='setting_github'), + url(r'^settings/github/(?P[0-9]+)/?$', setting.github_delete, name='setting_github_delete'), + url(r'^settings/notification$', RedirectView.as_view(pattern_name='setting_notification')), + url(r'^settings/notification/$', setting.notification, name='setting_notification'), + url(r'^settings/notification/setting/?$', setting.notification_setting, name='setting_notification_setting'), + url(r'^settings/ssh$', RedirectView.as_view(pattern_name='setting_ssh')), + url(r'^settings/ssh/$', setting.ssh, name='setting_ssh'), + url(r'^settings/ssh/(?P[0-9]+)/?$', setting.ssh_delete, name='setting_ssh_delete'), + url(r'^settings/codereview$', RedirectView.as_view(pattern_name='setting_codereview')), + url(r'^settings/codereview/$', setting.codereview, name='setting_codereview'), + url(r'^settings/codereview/setting/?$', setting.codereview_setting, name='setting_codereview_setting'), + url(r'^hub/beacon/?$', hub.beacon, {'path': ''}), + url(r'^hub/beacon/(?P.*)$', hub.beacon, name='hub_beacon'), + url(r'^hub/stat/?$', hub.stat_index, name='hub_stat_index'), + url(r'^hub/stat/source/?$', hub.stat_source, name='hub_stat_source'), + url(r'^hub/emoji/?$', hub.emoji, name='hub_emoji'), + url(r'^hub/public_timeline/?$', hub.public_timeline, name='hub_public_timeline'), + url(r'^hub/yours/?$', hub.yours, name='hub_yours'), + url(r'^hub/search/?$', hub.search, name='hub_search'), + url(r'^hub/create/?$', hub.create, name='hub_create'), + url(r'^hub/future/?$', hub.future, name='hub_future'), + url(r'^hub/add_team/?$', hub.add_team, name='hub_add_team'), + url(r'^hub/my_issues/?$', hub.my_issues), + url(r'^hub/my_issues/repos/?$', hub.my_issues, name='hub_my_issues'), + url(r'^hub/my_issues/assigned/?$', hub.my_issues_assigned, name='hub_my_issues_assigned'), + url(r'^hub/my_issues/created_by/?$', hub.my_issues_created_by, name='hub_my_issues_created_by'), + url(r'^hub/my_issues/participated/?$', hub.my_issues_participated, name='hub_my_issues_participated'), + url(r'^hub/my_pull_requests/?$', hub.my_pull_requests, name='hub_my_pull_requests'), + + # team + url(r'^teams/?$', team.teams, name="team_teams"), + url(r'^teams/(?P\w+)/?$', hub_team.team_index, name="hub_team_team_index"), + url(r'^teams/(?P\w+)/pulls/?$', hub_team.team_pulls, name="hub_team_team_pulls"), + url(r'^teams/(?P\w+)/pulls/closed/?$', hub_team.team_pulls_closed, name="hub_team_team_pulls_closed"), + url(r'^teams/(?P\w+)/issues/?$', hub_team.team_issues, name="hub_team_team_issues"), + url(r'^teams/(?P\w+)/issues/repos/?$', hub_team.team_issues, name="hub_team_team_issues"), + url(r'^teams/(?P\w+)/issues/assigned/?$', hub_team.team_issues, name="hub_team_team_issues"), + url(r'^teams/(?P\w+)/issues/created_by/?$', hub_team.team_issues, name="hub_team_team_issues"), + url(r'^teams/(?P\w+)/issues/new/?$', hub_team.team_issues_new, name="hub_team_team_issues_new"), + url(r'^teams/(?P\w+)/issues/create/?$', hub_team.team_issues_create, name="hub_team_team_issues_create"), + url(r'^teams/(?P\w+)/issues/(?P[0-9]+)/?$', team_issue.issue, name="team_issue_issue"), + url(r'^teams/(?P\w+)/issues/(?P[0-9]+)/upvote/?$', team_issue.issue_upvote, name="team_issue_issue_upvote"), + url(r'^teams/(?P\w+)/issues/(?P[0-9]+)/comment/?$', team_issue.issue_comment, name="team_issue_issue_comment"), + url(r'^teams/(?P\w+)/issues/(?P[0-9]+)/tag/?$', team_issue.issue_tag, name="team_issue_issue_tag"), + url(r'^teams/(?P\w+)/issues/(?P[0-9]+)/join/?$', team_issue.issue_join, name="team_issue_issue_join"), + url(r'^teams/(?P\w+)/issues/(?P[0-9]+)/leave/?$', team_issue.issue_leave, name="team_issue_issue_leave"), + url(r'^teams/(?P\w+)/issues/(?P[0-9]+)/assign/?$', team_issue.issue_assign, name="team_issue_issue_assign"), + # FIXME(xutao) team milestone is working? + url(r'^teams/(?P\w+)/issues/(?P[0-9]+)/milestone/?$', team_issue.issue_milestone, name="team_issue_issue_milestone"), + url(r'^teams/(?P\w+)/issue_comments/(?P[0-9]+)/edit/?$', team_issue.comment_edit, name="team_issue_comment_edit"), + url(r'^teams/(?P\w+)/issue_comments/(?P[0-9]+)/delete/?$', team_issue.comment_delete, name="team_issue_comment_delete"), + + # project + url(r'^(?P\w+)/(?P\w+)/blob/(?P\w+)/(?P.*)$', project.ProjectBlobView.as_view(), name="project_blob"), + url(r'^(?P\w+)/(?P\w+)/edit/(?P\w+)/(?P.*)$', project.ProjectEditView.as_view(), name="project_edit"), + url(r'^(?P\w+)/(?P\w+)/blame/(?P\w+)/(?P.*)$', project.ProjectBlameView.as_view(), name="project_blame"), + url(r'^(?P\w+)/(?P\w+)/raw/(?P\w+)/(?P.*)$', project.ProjectRawView.as_view(), name="project_raw"), + url(r'^(?P\w+)/(?P\w+)/commits/(?P\w+)/(?P.*)$', project.ProjectCommitsView.as_view(), name="project_commits"), + url(r'^(?P\w+)/(?P\w+)/tree/(?P[\w\-]+)/(?P.*)$', project.ProjectTreeView.as_view(), name="project_tree"), + url(r'^(?P\w+)/(?P\w+)/browsefiles/?$', project.browsefiles, name="project_browsefiles"), + url(r'^(?P\w+)/(?P\w+)/code_review/(?P[0-9]+)/delete/?$', project.codereview_delete, name="project_codereview_delete"), + url(r'^(?P\w+)/(?P\w+)/code_review/(?P[0-9]+)/edit/?$', project.codereview_edit, name="project_codereview_edit"), + url(r'^(?P\w+)/(?P\w+)/comments/?$', project.comment, name="project_comment"), + url(r'^(?P\w+)/(?P\w+)/comments/new/?$', project.comment_new, name="project_comment_new"), + url(r'^(?P\w+)/(?P\w+)/comments/(?P[0-9]+)/?$', project.comment_delete, name="project_comment_delete"), + url(r'^(?P\w+)/(?P\w+)/compare/?$', project.compare_index, name="project_compare_index"), + url(r'^(?P\w+)/(?P\w+)/compare/(?P.*)$', project.compare_range, name="project_compare_range"), + url(r'^(?P\w+)/(?P\w+)/issues/?$', project_issue.issues_index, name="project_issues"), + url(r'^(?P\w+)/(?P\w+)/issues/new/?$', project_issue.issues_new, name="project_issues_new"), + url(r'^(?P\w+)/(?P\w+)/issues/create/?$', project_issue.issues_create, name="project_issues_create"), + url(r'^(?P\w+)/(?P\w+)/issues/assigned/?$', project_issue.issues_assigned, name="project_issues_assigned"), + url(r'^(?P\w+)/(?P\w+)/issues/search/?$', project_issue.issues_search, name="project_issues_search"), + url(r'^(?P\w+)/(?P\w+)/issues/created_by/?$', project_issue.issues_created_by, name="project_issues_created_by"), + url(r'^(?P\w+)/(?P\w+)/issues/mentioned/?$', project_issue.issues_metioned, name="project_issues_mentioned"), + url(r'^(?P\w+)/(?P\w+)/issues/tags/(?P[\w:]+)/?$', project_issue.issues_tags_tag, name="project_issues_tags_tag"), + url(r'^(?P\w+)/(?P\w+)/issues/(?P[0-9]+)/?$', project_issue.issue, name="project_issue_index"), + url(r'^(?P\w+)/(?P\w+)/issues/(?P[0-9]+)/upvote/?$', project_issue.issue_upvote, name="project_issue_upvote"), + url(r'^(?P\w+)/(?P\w+)/issues/(?P[0-9]+)/assign/?$', project_issue.issue_assign, name="project_issue_assign"), + url(r'^(?P\w+)/(?P\w+)/issues/(?P[0-9]+)/leave/?$', project_issue.issue_leave, name="project_issue_leave"), + url(r'^(?P\w+)/(?P\w+)/issues/(?P[0-9]+)/join/?$', project_issue.issue_join, name="project_issue_join"), + url(r'^(?P\w+)/(?P\w+)/issues/(?P[0-9]+)/tag/?$', project_issue.issue_tag, name="project_issue_tag"), + url(r'^(?P\w+)/(?P\w+)/issues/(?P[0-9]+)/milestone/?$', project_issue.issue_milestone, name="project_issue_milestone"), + url(r'^(?P\w+)/(?P\w+)/issues/(?P[0-9]+)/comment/?$', project_issue.issue_comment, name="project_issue_comment"), + url(r'^(?P\w+)/(?P\w+)/issue_comments/(?P[0-9]+)/edit/?$', project_issue.issue_comments_comment_edit, name="project_issue_comments_comment_edit"), + url(r'^(?P\w+)/(?P\w+)/issue_comments/(?P[0-9]+)/delete/?$', project_issue.issue_comments_comment_delete, name="project_issue_comments_comment_delete"), + url(r'^(?P\w+)/(?P\w+)/watchers/?$', project.watchers, name="project_watchers"), + url(r'^(?P\w+)/(?P\w+)/forkers/?$', project.forkers, name="project_forkers"), + url(r'^(?P\w+)/(?P\w+)/archive/(?P\w+)/?$', project.archive, name="project_archive"), + url(r'^(?P\w+)/(?P\w+)/settings/?$', project_setting.index, name="project_setting_index"), + url(r'^(?P\w+)/(?P\w+)/settings/add_committer/?$', project_setting.add_committer, name="project_setting_add_committer"), + url(r'^(?P\w+)/(?P\w+)/settings/del_committer/?$', project_setting.del_committer, name="project_setting_del_committer"), + url(r'^(?P\w+)/(?P\w+)/settings/sphinx_docs/?$', project_setting.sphinx_docs, name="project_setting_sphinx_docs"), + url(r'^(?P\w+)/(?P\w+)/settings/hooks/?$', project_setting.hooks_index, name="project_setting_hooks_index"), + url(r'^(?P\w+)/(?P\w+)/settings/hooks/new/?$', project_setting.hooks_new, name="project_setting_hooks_new"), + url(r'^(?P\w+)/(?P\w+)/settings/hooks/(?P[0-9]+)/?$', project_setting.hooks_hook, name="project_setting_hooks_hook"), + url(r'^(?P\w+)/(?P\w+)/settings/conf/?$', project_setting.conf, name="project_setting_conf"), + url(r'^(?P\w+)/(?P\w+)/settings/pages/?$', project_setting.pages, name="project_setting_pages"), + url(r'^(?P\w+)/(?P\w+)/settings/transfer_project/?$', project_setting.transfer_project, name="project_setting_transfer_project"), + url(r'^(?P\w+)/(?P\w+)/settings/rename_project/?$', project_setting.rename_project, name="project_setting_rename_project"), + url(r'^(?P\w+)/(?P\w+)/settings/groups/?$', project_setting.groups_index, name="project_setting_groups_index"), + url(r'^(?P\w+)/(?P\w+)/settings/groups/destroy/?$', project_setting.groups_destory, name="project_setting_groups_destroy"), + # FIXME(xutao) move `^watch/?$` to user + url(r'^watch/?$', project.watch_index, name="project_watch_index"), + url(r'^watch/(?P[0-9]+)/?$', project.watch, name="project_watch"), + url(r'^fetch/(?P[0-9]+)/?$', project.fetch, name="project_fetch"), + + # gist + url(r'^gist/$', gist.index, name='gist_index'), + url(r'^gist/discover$', gist.discover, name='gist_discover'), + url(r'^gist/forked$', gist.forked, name='gist_forked'), + url(r'^gist/starred$', gist.starred, name='gist_starred'), + url(r'^gist/(?P\w+)/forked$', gist_user.forked, name='gist_user_forked'), + url(r'^gist/(?P\w+)/starred$', gist_user.starred, name='gist_user_starred'), + url(r'^gist/(?P\w+)/?$', gist_user.index, name='gist_user_index'), + url(r'^gist/(?P\w+)/(?P[0-9]+)/?$', gist_user.gist_index, name='gist_gist_index'), + url(r'^gist/(?P\w+)/(?P[0-9]+).js$', gist_embed.index, name='gist_embed_index'), + url(r'^gist/(?P\w+)/(?P[0-9]+)/comments/$', gist_comment.index, name='gist_comment_index'), + url(r'^gist/(?P\w+)/(?P[0-9]+)/comments/(?P[0-9]+)$', gist_comment.comment, name='gist_comment_comment'), + url(r'^gist/(?P\w+)/(?P[0-9]+)/revisions/$', gist_user.gist_revisions, name='gist_user_gist_revisions'), + url(r'^gist/(?P\w+)/(?P[0-9]+)/download$', gist_user.gist_download, name='gist_user_gist_download'), + url(r'^gist/(?P\w+)/(?P[0-9]+)/edit$', gist_user.gist_edit, name='gist_user_gist_edit'), + url(r'^gist/(?P\w+)/(?P[0-9]+)/delete$', gist_user.gist_delete, name='gist_user_gist_delete'), + url(r'^gist/(?P\w+)/(?P[0-9]+)/(?P\w+)$', gist_user.gist_index, name='gist_gist_index_revision'), + url(r'^gist/(?P\w+)/(?P[0-9]+)/raw/(?P\w+)/(?P.*)$', gist_raw.index, name='gist_raw_index'), + + # badge + url(r'^badge/?$', badge.timeline), + url(r'^badge/fetch_new/?$', badge.fetch_new, name='badge_fetch_new'), + url(r'^badge/all/?$', badge.all, name='badge_all'), + url(r'^badge/timeline/?$', badge.timeline, name='badge_timeline'), + url(r'^badge/badges/?$', badge.badges, name='badge_badges'), + url(r'^badge/items/?$', badge.items, name='badge_items'), + url(r'^badge/count/?$', badge.count, name='badge_count'), + url(r'^badge/add/?$', badge.add, name='badge_add'), + url(r'^badge/(?P[0-9]+)/?$', badge.badge_index, name='badge_badge_index'), + url(r'^badge/(?P[0-9]+)/people/?$', badge.badge_people, name='badge_badge_people'), + + # misc + url(r'^mirrors/?$', views.mirrors, name="views_mirrors"), + url(r'^j/fav/?$', views.j_fav, name="views_j_fav"), + url(r'^j/pull/(?P[0-9]+)/?$', views.j_pull_edit, name="views_j_pull_edit"), + # TODO(xutao) move '^j/issue/(?P[0-9]+)/edit/?$' to issue or api + url(r'^j/issue/(?P[0-9]+)/edit/?$', views.j_issue_edit, name="views_j_issue_edit"), + url(r'^j/issue/delete_tag/?$', views.j_issue_delete_tag, name="views_j_issue_delete_tag"), + url(r'^j/hooks/(?P[0-9]+)/telchar/?$', views.j_hooks_telchar, name="views_j_hooks_telchar"), + url(r'^j/chat/delete_room/?$', views.j_chat_delete_room, name="views_j_chat_delete_room"), + url(r'^j/chat/(?P\w+)/?$', views.j_chat_room, name="views_j_chat_room"), + url(r'^j/more/notify/(?P[0-9]+)/?$', views.j_more_notify, name="views_j_more_notify"), + url(r'^j/more/team/(?P\w+)/(?P[0-9]+)/?$', views.j_more_team, name="views_j_more_team"), + url(r'^j/more/userfeed/(?P[0-9]+)/?$', views.j_more_userfeed, name="views_j_more_userfeed"), + url(r'^j/more/pub/(?P[0-9]+)/?$', views.j_more_pub, name="views_j_more_pub"), + url(r'^m/?$', views.m_index, name="views_m_index"), + url(r'^m/public_timeline/?$', views.m_public_timeline, name="views_m_public_timeline"), + url(r'^m/actions/?$', views.m_actions, name="views_m_actions"), + url(r'^admin/', admin.site.urls), + + # Match last + url(r'^(?P\w+)/(?P\w+)$', RedirectView.as_view(pattern_name='project_index')), + url(r'^(?P\w+)/(?P\w+)/$', project.ProjectTreeView.as_view(), {'revision': '', 'path': ''}, name="project_index"), +] diff --git a/vilya/views/__init__.py b/vilya/views/__init__.py index 5159ec9..eb2476c 100644 --- a/vilya/views/__init__.py +++ b/vilya/views/__init__.py @@ -8,23 +8,16 @@ from vilya.libs.text import render_markdown from vilya.libs.template import st, request from vilya.views.uis.graph import GraphUI -from vilya.views.uis.browsefiles import BrowsefilesUI from vilya.views.uis.sphinx_docs import SphinxDocsUI from vilya.views.uis.docs import DocsUI from vilya.views.uis.source import SourceUI from vilya.views.uis.commit import CommitUI from vilya.views.uis.pages import PagesUI -from vilya.views.uis.watchers import WatchersUI, ForkersUI from vilya.views.uis.pull import PullUI, PullsUI from vilya.views.uis.dashboard import DashboardUI -from vilya.views.uis.compare import CompareUI -from vilya.views.uis.comments import CommentUI from vilya.views.uis.line_comments import LineCommentUI -from vilya.views.uis.code_review import CodeReviewUI from vilya.views.uis.pr_comment import PrCommentUI from vilya.views.uis.issue import IssueBoardUI, IssueCommentUI -from vilya.views.uis.settings import SettingsUI -from vilya.views.uis.archive import ArchiveUI from vilya.views.util import jsonize from vilya.views.fair import FairUI from vilya.views.hub.search_beta import SrcIndexUI, SearchUI @@ -38,10 +31,7 @@ ISSUES_COUNT_PER_PAGE = 5 -_q_exports = ['hub', 'people', 'badge', 'api', 'page', 'preview', - 'watch', 'settings', 'praise', 'gist', 'oauth', - 'j', 'm', 'watching', 'fetch', 'mirrors', 'trello', - 'teams', 'favorites', 'login', 'logout', 'register'] +_q_exports = ['hub', 'api', 'preview', 'oauth', 'teams'] class StaticUI(object): @@ -108,6 +98,8 @@ def _q_index(request): def _q_lookup(request, name): if name == 'static': return StaticUI(request) + if name == 'favicon.ico': + return StaticUI(request, '/favicon.ico') if name == 'fair': return FairUI(request) if CodeDoubanProject.exists(name): @@ -131,11 +123,11 @@ def _q_lookup(self, request, url_part): class CodeUI: _q_exports = [ - 'hooks', 'graph', 'commit', 'pull', 'newpull', 'comments', - 'compare', 'line_comments', 'settings', 'browsefiles', 'pulls', - 'docs', 'remove', 'code_review', 'pr_comment', 'issues', - 'issue_comments', 'watchers', 'forkers', 're_index_docs', 'src_index', - 'search', 'pages', 'xdocs', 'dashboard', 'archive' + 'hooks', 'graph', 'commit', 'pull', 'newpull', + 'line_comments', 'pulls', + 'docs', 'remove', 'pr_comment', 'issues', + 'issue_comments', 're_index_docs', 'src_index', + 'search', 'pages', 'xdocs', 'dashboard', ] def __init__(self, proj_name): @@ -168,10 +160,6 @@ def check_permission(self, request, proj_name): and not project.is_admin(user.username): return request.redirect(User.create_login_url(request.url)) - @property - def compare(self): - return CompareUI(self.proj_name) - @property def commit(self): return CommitUI(self.proj_name) @@ -192,14 +180,6 @@ def issue_comments(self): def graph(self): return GraphUI(self.proj_name) - @property - def watchers(self): - return WatchersUI(self.proj_name) - - @property - def forkers(self): - return ForkersUI(self.proj_name) - @property def pull(self): return PullUI(self.proj_name) @@ -216,30 +196,14 @@ def pulls(self): def dashboard(self): return DashboardUI(self.proj_name) - @property - def comments(self): - return CommentUI(self.proj_name) - @property def line_comments(self): return LineCommentUI(self.proj_name) - @property - def code_review(self): - return CodeReviewUI(self.proj_name) - @property def pr_comment(self): return PrCommentUI(self.proj_name) - @property - def settings(self): - return SettingsUI(self.proj_name) - - @property - def browsefiles(self): - return BrowsefilesUI(self.proj_name) - @property def docs(self): return SphinxDocsUI(self.proj_name) @@ -282,10 +246,6 @@ def remove(self, request): return dict(r=0, err='该项目仍有未关闭的Pull request,请关闭后再删除项目。') # noqa return dict(r=0, err='') - @property - def archive(self): - return ArchiveUI(self.proj_name) - def preview(request): return render_markdown(request.get_form_var('text', '').decode('utf-8')).encode('utf-8') # noqa diff --git a/vilya/views/badge.py b/vilya/views/badge.py deleted file mode 100644 index bf92fba..0000000 --- a/vilya/views/badge.py +++ /dev/null @@ -1,115 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import absolute_import -from datetime import datetime, timedelta -import itertools -import json -import os - -from vilya.models.badge import Badge -from vilya.models import badge_timeline -from vilya.models.user import User -from vilya.libs.template import st - -_q_exports = ['fetch_new', 'all', 'timeline', 'badges', 'items', 'count', - 'add'] - - -def fetch_new(request): - user = request.user - if user: - return json.dumps([{'bid': str(b.id)} for b in user.get_new_badges()]) - return '' - - -def all(request): - user = request.user - badges = Badge.get_all_badges() - return st('badge/all.html', **locals()) - - -def timeline(request): - today = datetime.now().date() - yesterday = today - timedelta(days=1) - return st('badge/timeline.html', **locals()) - - -def badges(request): - return json.dumps(badge_timeline.get_all_badges()) - - -def items(request): - index = int(request.get_form_var('ind', default=0)) - increment = int(request.get_form_var('inc', default=10)) - name = request.get_form_var('n', default='') - item_list = badge_timeline.get_all_items(index, increment, name) - item_list = [list(item) + [User(item[1]).avatar_url] for item in item_list] - items = [] - for k, g in itertools.groupby(item_list, lambda row: row[3].date()): - items.append(list(g)) - datehandler = lambda obj: obj.isoformat( - ) if isinstance(obj, datetime) else None - return json.dumps(items, default=datehandler) - - -def count(request): - name = request.get_form_var('n', default='') - return json.dumps(badge_timeline.get_item_count(name)) - - -def add(request): - if request.method == 'POST': - name = request.get_form_var('name', '').strip() - found = Badge.get_by_name(name) - if found: - return request.redirect('/badge/%s/' % found.id) - - summary = request.get_form_var('summary') - filename = request.get_form_var("picfile").tmp_filename - content = open(filename).read() - - new = Badge.add(name, summary) - root = os.environ['DAE_APPROOT'] - pic_path = '%s/hub/static/img/badges/%s.png' % (root, new.id) - open(pic_path, 'w').write(content) - - return request.redirect('/badge/%s/' % new.id) - return st('badge/add.html', request=request) - - -class BadgeUI(object): - _q_exports = ['people'] - - def __init__(self, badge): - self.badge = badge - - def _q_index(self, request): - current_user = request.user - badge = self.badge - items = badge.get_awarded_items() - users = [User(item.item_id) for item in items] - return st('badge/badge.html', **locals()) - - def people(self, request): - current_user = request.user - badge = self.badge - if not current_user: - return request.redirect('/badge/%s/' % badge.id) - if current_user.name not in ['liwanjin', 'xutao']: - return request.redirect('/badge/%s/' % badge.id) - if request.method != 'POST': - return request.redirect('/badge/%s/' % badge.id) - name = request.get_form_var('name') - reason = request.get_form_var('reason') - if not name: - return request.redirect('/badge/%s/' % badge.id) - if not User.check_exist(name): - return request.redirect('/badge/%s/' % badge.id) - badge.award(name, reason=reason) - return request.redirect('/badge/%s/' % badge.id) - - -def _q_lookup(request, id): - b = Badge.get(id) - if b: - return BadgeUI(b) diff --git a/vilya/views/django/__init__.py b/vilya/views/django/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/vilya/views/django/badge.py b/vilya/views/django/badge.py new file mode 100644 index 0000000..e16d919 --- /dev/null +++ b/vilya/views/django/badge.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- + +import os +import json +import itertools +from django.http import HttpResponse +from django.http import HttpResponseRedirect +from vilya.libs.template import st + + +def fetch_new(request): + user = request.user + if user: + return HttpResponse(json.dumps([{'bid': str(b.id)} for b in user.get_new_badges()])) + return HttpResponse('') + + +def all(request): + from vilya.models.badge import Badge + user = request.user + badges = Badge.get_all_badges() + return HttpResponse(st('badge/all.html', **locals())) + + +def timeline(request): + from datetime import datetime, timedelta + today = datetime.now().date() + yesterday = today - timedelta(days=1) + return HttpResponse(st('badge/timeline.html', **locals())) + + +def badges(request): + from vilya.models import badge_timeline + return HttpResponse(json.dumps(badge_timeline.get_all_badges())) + + +def items(request): + from vilya.models import badge_timeline + from datetime import datetime + from vilya.models.user import User + index = int(request.GET.get('ind', 0)) + increment = int(request.GET.get('inc', 10)) + name = request.GET.get('n', '') + item_list = badge_timeline.get_all_items(index, increment, name) + item_list = [list(item) + [User(item[1]).avatar_url] for item in item_list] + items = [] + for k, g in itertools.groupby(item_list, lambda row: row[3].date()): + items.append(list(g)) + datehandler = lambda obj: obj.isoformat( + ) if isinstance(obj, datetime) else None + return HttpResponse(json.dumps(items, default=datehandler)) + + +def count(request): + from vilya.models import badge_timeline + name = request.GET.get('n', '') + return HttpResponse(json.dumps(badge_timeline.get_item_count(name))) + + +def add(request): + from vilya.models.badge import Badge + if request.method == 'POST': + name = request.POST.get('name', '').strip() + found = Badge.get_by_name(name) + if found: + return HttpResponseRedirect('/badge/%s/' % found.id) + + summary = request.POST.get('summary') + filename = request.POST.get("picfile").tmp_filename + content = open(filename).read() + + new = Badge.add(name, summary) + # FIXME(xutao) upload dir + root = os.environ['DAE_APPROOT'] + pic_path = '%s/hub/static/img/badges/%s.png' % (root, new.id) + open(pic_path, 'w').write(content) + + return HttpResponseRedirect('/badge/%s/' % new.id) + return HttpResponse(st('badge/add.html', request=request)) + + +def badge_index(request, id): + from vilya.models.badge import Badge + from vilya.models.user import User + badge = Badge.get(id) + current_user = request.user + items = badge.get_awarded_items() + users = [User(item.item_id) for item in items] + return HttpResponse(st('badge/badge.html', **locals())) + + +def badge_people(request, id): + from vilya.models.badge import Badge + from vilya.models.user import User + current_user = request.user + badge = Badge.get(id) + if not current_user: + return HttpResponseRedirect('/badge/%s/' % badge.id) + if request.method != 'POST': + return HttpResponseRedirect('/badge/%s/' % badge.id) + name = request.POST.get('name') + reason = request.POST.get('reason') + if not name: + return HttpResponseRedirect('/badge/%s/' % badge.id) + if not User.check_exist(name): + return HttpResponseRedirect('/badge/%s/' % badge.id) + badge.award(name, reason=reason) + return HttpResponseRedirect('/badge/%s/' % badge.id) diff --git a/vilya/views/django/gist.py b/vilya/views/django/gist.py new file mode 100644 index 0000000..69c3812 --- /dev/null +++ b/vilya/views/django/gist.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- + +from django.http import HttpResponse +from django.http import HttpResponseRedirect +from django.views.decorators.csrf import csrf_exempt +from vilya.libs.template import st + + +@csrf_exempt +def index(request): + from vilya.views.util import is_mobile_device + from vilya.models.gist import Gist + # FIXME(xutao) translate django user to quixote user + user = request.user + if request.method == 'POST': + desc, is_public, names, contents, oids = _get_req_gist_data(request) + owner_id = user and user.username or Gist.ANONYMOUS + gist = Gist.add(desc, owner_id, is_public, names, contents) + return HttpResponseRedirect(gist.url) + + tdt = dict(request=request, gists=[], user=user) + if user: + gists = Gist.gets_by_owner(user.username, limit=4) + tdt.update(dict(gists=gists)) + + if is_mobile_device(request): + return HttpResponse(st('/m/gist/index.html', **tdt)) + return HttpResponse(st('/gist/index.html', **tdt)) + + +def discover(request): + return _discover(request) + + +def forked(request): + return _discover(request) + + +def starred(request): + return _discover(request) + + +def _get_req_gist_data(request): + _form = request.POST + desc = _form.get('desc', '') + is_public = _form.get('gist_public', '1') + gist_names = _form.getlist('gist_name', '') + gist_contents = _form.getlist('gist_content', '') + gist_oids = _form.getlist('oid', '') + return (desc, is_public, gist_names, gist_contents, gist_oids) + + +def _discover(request): + import inspect + from vilya.models.gist import Gist + # FIXME(xutao) translate django user to quixote user + user = request.user + name = inspect.stack()[1][3] + (page, start, link_prev, link_next, sort, + direction) = make_page_args(request, name) + gists = Gist.discover(name, sort, direction, start) + tdt = dict( + request=request, + gists=gists, + page=page, + link_prev=link_prev, + link_next=link_next, + sort=sort, + direction=direction, + user=user + ) + return HttpResponse(st('/gist/gists.html', **tdt)) + + +def make_page_args(request, name, ext=''): + page = request.GET.get('page', 1) + start = 5 * (int(page) - 1) + link_prev = _make_links(name, int(page) - 1, ext=ext) + link_next = _make_links(name, int(page) + 1, ext=ext) + sort = request.GET.get('sort', 'created') + direction = request.GET.get('direction', 'desc') + return (page, start, link_prev, link_next, sort, direction) + + +def _make_links(name, page, ext=''): + if page < 1: + return '' + if page and page >= 1: + if ext: + return '/gist/%s/%s/?page=%s' % (name, ext, page) + else: + return '/gist/%s/?page=%s' % (name, page) diff --git a/vilya/views/django/gist_comment.py b/vilya/views/django/gist_comment.py new file mode 100644 index 0000000..c1a0d0b --- /dev/null +++ b/vilya/views/django/gist_comment.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- + +from django.http import JsonResponse +from django.http import HttpResponseRedirect +from django.http import HttpResponseForbidden +from django.views.decorators.csrf import csrf_exempt + + +@csrf_exempt +def index(request, username, id): + from vilya.models.gist import Gist + from vilya.models.gist_comment import GistComment + + user = request.user + gist = Gist.get(id) + + if request.method == 'POST': + content = request.POST.get('content', '') + if content: + GistComment.add(gist.id, user.username, content) + return HttpResponseRedirect(gist.url) + + +@csrf_exempt +def comment(request, username, id, comment_id): + from vilya.models.gist import Gist + from vilya.models.gist_comment import GistComment + + user = request.user + gist = Gist.get(id) + + if request.method == 'POST': + act = request.POST.get('act', None) + if act and act in ('delete', 'update'): + comment = GistComment.get(comment_id) + if act == 'delete' and comment: + if comment.can_delete(user.username): + comment.delete() + return JsonResponse({'r': 1}) + raise HttpResponseForbidden("Unable to delete comment %s" % comment_id) + return HttpResponseRedirect(gist.url) diff --git a/vilya/views/django/gist_embed.py b/vilya/views/django/gist_embed.py new file mode 100644 index 0000000..e5aadbe --- /dev/null +++ b/vilya/views/django/gist_embed.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- + +from django.http import Http404 +from django.http import HttpResponse +from django.http import HttpResponseRedirect +from django.http import HttpResponseServerError +from django.http import StreamingHttpResponse +from vilya.libs.template import st +from vilya.config import DOMAIN + + +EMBED_CSS = """ + + +""" % (DOMAIN, DOMAIN) + +EMBED_HEAD = "
    " +EMBED_FOOTER = "
    " + +SRC_FORMAT = """ +
    +
    +
    %s
    +
    + view raw + %s # noqa + This Gist brought to you by Code. +
    +
    +
    +""" + + +def index(request, username, id): + from vilya.libs.text import highlight_code + from vilya.models.gist import Gist + gist_id = id + + response = StreamingHttpResponse(content_type='text/javascript') + response['Content-Disposition'] = 'filename=code_gist_%s.tar.gz' % id + response['Expires'] = 'Sun, 1 Jan 2006 01:00:00 GMT' + response['Pragma'] = 'no-cache' + response['Cache-Control'] = 'must-revalidate, no-cache, private' + + gist = Gist.get(gist_id) + if not gist_id.isdigit() or not gist: + response.streaming_content = "document.write('NOT EXIST GIST')" # noqa + return response + + html = EMBED_CSS + EMBED_HEAD % gist.id + for path in gist.files: + path = path.encode('utf8') + # TODO: clean this + src = gist.get_file(path, rev='HEAD') + src = highlight_code(path, src) + src = src.replace('"', '\"').replace("'", "\'") + html += SRC_FORMAT % (src, DOMAIN, gist.id, path, DOMAIN, + gist.id, path, path, gist.url, DOMAIN) + + html += EMBED_FOOTER + html = html.replace('\n', '\\n') + response.streaming_content = "document.write('%s')" % html + return response diff --git a/vilya/views/django/gist_raw.py b/vilya/views/django/gist_raw.py new file mode 100644 index 0000000..0af661a --- /dev/null +++ b/vilya/views/django/gist_raw.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +from django.http import Http404 +from django.http import HttpResponseServerError +from django.http import StreamingHttpResponse + + +def index(request, username, id, revision, filename): + from vilya.models.gist import Gist + gist = Gist.get(id) + path = filename + + try: + # TODO: clean this + text = gist.get_file(path, rev=revision) + except IOError: + return HttpResponseServerError() + if isinstance(text, bool) and text is False: + raise Http404 + + response = StreamingHttpResponse(content_type='text/plain; charset=utf-8') + response.streaming_content = text.encode('utf-8') + return response + diff --git a/vilya/views/django/gist_user.py b/vilya/views/django/gist_user.py new file mode 100644 index 0000000..8b9cc2d --- /dev/null +++ b/vilya/views/django/gist_user.py @@ -0,0 +1,220 @@ +# -*- coding: utf-8 -*- + +from django.http import HttpResponse +from django.http import Http404 +from django.http import HttpResponseRedirect +from django.http import HttpResponseForbidden +from django.http import StreamingHttpResponse +from django.views.decorators.csrf import csrf_exempt +from vilya.libs.template import st +from vilya.views.django.gist import make_page_args, _make_links, _get_req_gist_data + + +def index(request, username): + from vilya.models.gist import Gist + return _render(username, request, Gist.gets_by_owner) + + +def forked(request, username): + from vilya.models.gist import Gist + return _render(username, request, Gist.forks_by_user) + + +def starred(request, username): + from vilya.models.gist import Gist + return _render(username, request, Gist.stars_by_user) + + +def public(request, username): + from vilya.models.gist import Gist + return _render(username, request, Gist.publics_by_user) + + +def secret(request, username): + from vilya.models.gist import Gist + current_user = request.user + if not current_user or current_user.username != username: + return HttpResponseRedirect('/gist/%s' % username) + return _render(username, request, Gist.secrets_by_user) + + +def _render(username, request, func): + from vilya.views.util import is_mobile_device + from vilya.models.user import User + from vilya.models.gist import Gist + name = username + + # FIXME(xutao) check user + user = User(name) + + current_user = request.user + is_self = current_user and current_user.username == name + ext = request.get_path().split('/')[-1] + (page, start, link_prev, link_next, sort, direction) =\ + make_page_args(request, name, ext=ext) + n_all = Gist.count_user_all(name, is_self) + n_fork = Gist.count_user_fork(name) + n_star = Gist.count_user_star(name) + + if sort not in ('created', 'updated') \ + or direction not in ('desc', 'asc'): + raise Http404() + + gists = func(name, start=start, limit=5, sort=sort, direction=direction) + + tdt = { + 'request': request, + 'gists': gists, + 'user': user, + 'page': int(page), + 'link_prev': link_prev, + 'link_next': link_next, + 'n_all': n_all, + 'n_fork': n_fork, + 'n_star': n_star, + 'sort': sort, + 'direction': direction + } + if is_mobile_device(request): + return HttpResponse(st('/m/gist/user_gists.html', **tdt)) + return HttpResponse(st('/gist/user_gists.html', **tdt)) + + +def gist_index(request, username, id, revision=None): + from vilya.models.gist import Gist + from vilya.views.util import is_mobile_device + + gist = Gist.get(id) + user = request.user + if revision and gist.repo.is_commit(revision): + tdt = {'request': request, + 'gist': gist, + 'ref': revision, + 'user': user} + return HttpResponse(st('/gist/gist_detail.html', **tdt)) + tdt = dict(request=request, gist=gist, ref='master', user=user) + if is_mobile_device(request): + return HttpResponse(st('/m/gist/gist_detail.html', **tdt)) + return HttpResponse(st('/gist/gist_detail.html', **tdt)) + + +def gist_revisions(request, username, id): + from vilya.models.gist import Gist + + user = request.user + + gist = Gist.get(id) + page = int(request.GET.get('page', 1)) + skip = 3 * (page - 1) + revlist = gist.get_revlist_with_renames(max_count=3, skip=skip) + link_prev = _make_links(id, int(page) - 1, ext="revisions") + if revlist: + link_next = _make_links(id, int(page) + 1, ext="revisions") + else: + link_next = '' + content = [] + for r in revlist: + # FIXME: try-except ? + content.append(gist.repo.get_diff(r.sha, rename_detection=True)) + tdt = { + 'request': request, + 'gist': gist, + 'content': content, + 'revlist': revlist, + 'link_prev': link_prev, + 'link_next': link_next, + 'user': user, + 'current_user': user, + } + return HttpResponse(st('/gist/gist_revisions.html', **tdt)) + + +def gist_forks(request, username, id): + from vilya.models.gist import Gist + user = request.user + gist = Gist.get(id) + tdt = dict(request=request, gist=gist, user=user) + return HttpResponse(st('/gist/gist_forks.html', **tdt)) + + +def gist_stars(request, username, id): + from vilya.models.gist import Gist + user = request.user + gist = Gist.get(id) + tdt = dict(request=request, gist=gist, user=user) + return HttpResponse(st('/gist/gist_stars.html', **tdt)) + + +def gist_fork(request, username, id): + from vilya.models.gist import Gist + user = request.user + gist = Gist.get(id) + new_gist = gist.fork(user.username) + return HttpResponseRedirect(new_gist.url) + + +def gist_unstar(request, username, id): + from vilya.models.gist import Gist + from vilya.models.gist_star import GistStar + user = request.user + gist = Gist.get(id) + star = GistStar.get_by_gist_and_user(id, user.username) + if star: + star.delete() + return HttpResponseRedirect(gist.url) + + +def gist_star(request, username, id): + from vilya.models.gist import Gist + from vilya.models.gist_star import GistStar + user = request.user + gist = Gist.get(id) + GistStar.add(id, user.username) + return HttpResponseRedirect(gist.url) + + +@csrf_exempt +def gist_edit(request, username, id): + from vilya.views.util import is_mobile_device + from vilya.models.gist import Gist + user = request.user + gist = Gist.get(id) + if not user or user.username != gist.owner_id: + raise HttpResponseForbidden() + + if request.method == 'POST': + desc, is_public, names, contents, oids = _get_req_gist_data( + request) + gist.update(desc, names, contents, oids) + return HttpResponseRedirect(gist.url) + + tdt = dict(request=request, gist=gist, user=user) + if is_mobile_device(request): + return HttpResponse(st('/m/gist/edit.html', **tdt)) + return HttpResponse(st('/gist/edit.html', **tdt)) + + +def gist_delete(request, username, id): + from vilya.models.gist import Gist + user = request.user + gist = Gist.get(id) + if not user or user.username != gist.owner_id: + raise HttpResponseForbidden() + gist.delete() + return HttpResponseRedirect('/gist/%s' % user.username) + + +def gist_download(request, username, id): + from vilya.models.gist import Gist + gist = Gist.get(id) + response = StreamingHttpResponse(content_type='application/x-gzip') + response['Content-Disposition'] = 'filename=code_gist_%s.tar.gz' % id + response.streaming_content = gist.repo.archive(name="code_gist_%s" % id) + return response + + +def gist_re_index(request, username, id): + # TODO(xutao) remove es support + from vilya.models.gist import Gist + gist = Gist.get(id) + return HttpResponseRedirect(gist.url) diff --git a/vilya/views/django/hub.py b/vilya/views/django/hub.py new file mode 100644 index 0000000..4017dbb --- /dev/null +++ b/vilya/views/django/hub.py @@ -0,0 +1,482 @@ +# -*- coding: utf-8 -*- + +import re +import json +from django.http import Http404 +from django.http import HttpResponse +from django.http import HttpResponseRedirect +from django.http import StreamingHttpResponse +from django.views.decorators.csrf import csrf_exempt +from vilya.libs.template import st + + +def emoji(request): + return HttpResponse(st('emoji.html', **locals())) + + +def public_timeline(request): + from vilya.models.feed import get_public_feed, PAGE_ACTIONS_COUNT + actions = get_public_feed().get_actions(stop=PAGE_ACTIONS_COUNT - 1) + return HttpResponse(st('public_timeline.html', **locals())) + + +def yours(request): + from vilya.models.feed import get_user_feed, PAGE_ACTIONS_COUNT + from vilya.models.project import CodeDoubanProject + user = request.user + actions = (get_user_feed(user.username) + .get_actions(stop=PAGE_ACTIONS_COUNT - 1)) + + your_projects = CodeDoubanProject.get_projects( + owner=user.username, sortby="lru") + watched_projects = CodeDoubanProject.get_watched_others_projects_by_user( + user=user.username, sortby='lru') + + badge_items = user.get_badge_items() + return HttpResponse(st('my_actions.html', **locals())) + + +def search(request): + from vilya.models.project import CodeDoubanProject + q = request.GET.get('q') + if q: + results = CodeDoubanProject.search_by_name(q) + return HttpResponse(st('search.html', **locals())) + +@csrf_exempt +def create(request): + from datetime import datetime + from vilya.models.project import CodeDoubanProject + from vilya.models.consts import ( + ORGANIZATION_PROJECT, + MIRROR_PROJECT, + PEOPLE_PROJECT, + MIRROR_STATE_CLONING, + MIRROR_NOT_PROXY) + from vilya.models.mirror import CodeDoubanMirror + user = request.user + if not user: + return request.redirect("/") + errors = "" + + template_filename = 'create.html' + + if request.method == "POST": + name = request.POST.get('name') + product = request.POST.get('product') + org_proj = request.POST.get('org_proj') + summary = request.POST.get('summary') + repo_url = request.POST.get('url') + fork_from = request.POST.get('fork_from') + intern_banned = request.POST.get('intern_banned', None) + with_proxy = request.POST.get('with_proxy', MIRROR_NOT_PROXY) + with_proxy = int(with_proxy) + mirror = None + + def add_people_project(project): + name = "%s/%s" % (project.owner_id, project.name) + _project = CodeDoubanProject.add( + name=name, owner_id=project.owner_id, + summary=project.summary, product=project.product, + intern_banned=project.intern_banned) + return _project + + def add_org_project(project): + _project = CodeDoubanProject.add( + name=name, owner_id=project.owner_id, + summary=project.summary, product=project.product, + intern_banned=project.intern_banned) + return _project + + def add_mirror_project(project): + name = "mirror/%s" % (project.name) + _project = CodeDoubanProject.add( + name=name, owner_id='mirror', summary=project.summary, + product=project.product, intern_banned=project.intern_banned, + mirror=project.mirror_url) + if _project: + CodeDoubanMirror.add(url=project.mirror_url, + state=MIRROR_STATE_CLONING, + project_id=_project.id, + with_proxy=project.mirror_proxy) + return _project + + def add_fork_project(project): + name = "%s/%s" % (project.owner_id, project.name) + _project = CodeDoubanProject.add( + name=name, owner_id=project.owner_id, summary=project.summary, + product=project.product, fork_from=project.fork_from, + intern_banned=project.intern_banned) + if _project: + fork_from_project = CodeDoubanProject.get(project.fork_from) + _project.update(project.summary, + project.product, + name, + fork_from_project.intern_banned) + return _project + + def validate_project(project_type, project): + error = '' + if project_type in (PEOPLE_PROJECT, ORGANIZATION_PROJECT): + error = project.validate() + elif project_type == MIRROR_PROJECT: + error = project.validate() + if not error: + error = CodeDoubanMirror.validate(project.mirror_url) + else: + error = project.validate() + return error + + def add_project(project): + _project = None + if project_type == PEOPLE_PROJECT: + _project = add_people_project(project) + elif project_type == ORGANIZATION_PROJECT: + _project = add_org_project(project) + elif project_type == MIRROR_PROJECT: + _project = add_mirror_project(project) + else: + _project = add_fork_project(project) + return _project + + project = CodeDoubanProject(None, name, user.username, summary, + datetime.now(), product, None, None, + fork_from=fork_from, + intern_banned=intern_banned, + mirror_url=repo_url, + mirror_proxy=with_proxy) + # FIXME: rename org_proj of html + project_type = org_proj + errors = validate_project(project_type, project) + if errors: + return HttpResponse(st(template_filename, **locals())) + + project = add_project(project) + if not project: + fork_from = '' + errors = 'project exists' + return HttpResponse(st(template_filename, **locals())) + + CodeDoubanProject.add_watch(project.id, user.name) + return HttpResponseRedirect('/%s/' % project.name) + + fork_from = '' + if request.POST.get('fork_from'): + fork_from = CodeDoubanProject.get(request.POST.get('fork_from')) + name = "%s/%s" % (user.name, fork_from.realname) + if CodeDoubanProject.exists(name): + return HttpResponseRedirect('/%s/' % name) + projects = CodeDoubanProject.gets_by_owner_id(user.name) + for p in projects: + if p.origin_project_id == fork_from.id and '/' in p.name: + return HttpResponseRedirect('/%s/' % p.name) + return HttpResponse(st(template_filename, **locals())) + + +def future(request): + return HttpResponse(st('future.html', **locals())) + + +# TODO(xutao) move to /teams/new +@csrf_exempt +def add_team(request): + from vilya.libs.signals import team_created_signal + from vilya.models.consts import TEAM_OWNER + from vilya.models.user import User + from vilya.models.team import Team + user = request.user + if not user: + return HttpResponseRedirect("/") + + errors = "" + uid = request.POST.get('uid') or '' + name = request.POST.get('name') or '' + description = request.POST.get('description') or '' + if request.method == "POST": + + teams = Team.gets() + team_uid_pattern = re.compile(r'[a-zA-Z0-9\_]*') + if not uid: + error = 'uid_not_exists' + elif not name: + error = 'name_not_exists' + elif uid != re.findall(team_uid_pattern, uid)[0]: + error = 'invilid_uid' + elif uid in [team.uid for team in teams]: + error = 'uid_existed' + elif User.check_exist(uid): + error = 'user_id_existed' + elif name in [team.name for team in teams]: + error = 'name_existed' + else: + team = Team.add(uid, name, description) + if team: + team_created_signal.send(user.name, + team_name=team.name, + team_uid=team.uid) + team.add_user(user, TEAM_OWNER) + return HttpResponseRedirect(team.url) + + return HttpResponse(st('/teams/add_team.html', **locals())) + + +def beacon(request, path): + if not path: + return HttpResponseRedirect('/hub/notification') + + from vilya.models.user import User + from vilya.models.notification import Notification + # cat hook.gif | base64 + EMAIL_HOOK_GIF = "R0lGODlhAQABAID/AP///wAAACwAAAAAAQABAAACAkQBADs=" + + url = path + parts = url.split('.') + if len(parts) == 2 and parts[1] == 'gif': + uid, ext = parts + username = request.GET.get('user') + user = User(username) if username else request.user + else: + raise Http404() + + if user and uid: + tokens = uid.split('-') + if tokens[0] == 'pullrequest': + project_name = '-'.join(tokens[1:-2]) + pull_number = tokens[-2] + Notification.mark_as_read_by_pull( + user.name, project_name, pull_number) + else: + Notification.mark_as_read(user.name, uid) + + response = StreamingHttpResponse(content_type='image/gif') + response.streaming_content = EMAIL_HOOK_GIF.decode('base64') + return response + + +def my_issues_filter(list_type): + from vilya.models.project_issue import ProjectIssue + from vilya.models.project import CodeDoubanProject + ISSUES_COUNT_PER_PAGE = 25 + + def index(request): + user = request.user + my_issues = [] + if user: + page = request.GET.get('page', 1) + state = request.GET.get("state", "open") + is_closed_tab = None if state == "open" else True + project_ids = CodeDoubanProject.get_ids(user.name) + # TODO: 下面的这些N,可以考虑先从 get 参数里拿一下,没有的话再重新读 + n_repos_issue = ProjectIssue.get_count_by_project_ids(project_ids, + state) + n_assigned_issue = user.get_count_assigned_issues(state) + n_created_issue = user.get_count_created_issues(state) + n_participated_issue = user.get_n_participated_issues(state) + total_issues = { + 'repos': n_repos_issue, + 'assigned': n_assigned_issue, + 'created_by': n_created_issue, + 'participated': n_participated_issue, + }[list_type] + n_pages = (total_issues - 1) / ISSUES_COUNT_PER_PAGE + 1 + dt = { + 'state': state, + 'limit': ISSUES_COUNT_PER_PAGE, + 'start': ISSUES_COUNT_PER_PAGE * (int(page) - 1), + } + if list_type == 'repos': + my_issues = ProjectIssue.gets_by_project_ids(project_ids, **dt) + else: + get_my_issues = { + 'assigned': user.get_assigned_issues, + 'created_by': user.get_created_issues, + 'participated': user.get_participated_issues, + }[list_type] + my_issues = get_my_issues(**dt) + return HttpResponse(st('issue/my_issues.html', **locals())) + return index + + +my_issues = my_issues_filter('repos') +my_issues_assigned = my_issues_filter('assigned') +my_issues_created_by = my_issues_filter('created_by') +my_issues_participated = my_issues_filter('participated') + + +def my_pull_requests(request): + from random import shuffle + from vilya.models.consts import MY_PULL_REQUESTS_TAB_INFO + + user = request.user + if user: + list_type = request.GET.get("list_type", "invited") + + n_invited = user.n_open_invited + n_participated = user.n_open_participated + n_yours = user.n_user_open_submit_pull_requests + counts = [n_invited, n_participated, n_yours, None] + tab_info = [] + for tab, count in zip(MY_PULL_REQUESTS_TAB_INFO, counts): + tab.update(count=count) + tab_info.append(tab) + + if list_type == "participated": + tickets = user.get_participated_pull_requests() + elif list_type == "yours": + tickets = user.get_user_submit_pull_requests() + elif list_type == "explore": + from vilya.models.ticket import Ticket + tickets = Ticket.gets_all_opened() + ticket_total_len = len(tickets) + shuffle(tickets) + else: + tickets = user.get_invited_pull_requests() + is_closed_tab = False + ticket_total_len = len(tickets) + return HttpResponse(st('my_pull_requests.html', **locals())) + + +def notification(request): + from vilya.models.notification import Notification + from vilya.models.actions.base import ActionScope + from vilya.models.actions import migrate_notif_data + user = request.user + if user: + all = request.POST.get('all') + scope = request.POST.get('scope') + unread = not all or all != '1' + scope = ActionScope.getScope(scope) or '' # 不带scope则默认所有 + + actions = Notification.get_data(user.name) + + # 迁移数据 + all_actions = [migrate_notif_data(action, user.name) + for action in actions] + + if scope: + actions = [action for action in all_actions + if action.get('scope') == scope] + else: + actions = all_actions + + if unread: + actions = [action for action in actions if not action.get('read')] + count_dict = {s: len([a for a in all_actions + if a.get('scope') == s and not a.get('read')]) + for s in ActionScope.all_scopes} + else: + count_dict = {s: len([a for a in all_actions + if a.get('scope') == s]) + for s in ActionScope.all_scopes} + count_dict['all'] = sum(count_dict.values()) + + return HttpResponse(st('notifications.html', **locals())) + else: + return HttpResponseRedirect("/hub/teams") + + +@csrf_exempt +def notification_mark(request): + ''' mark by uids ''' + from vilya.models.notification import Notification + user = request.user + if user: + uids = request.POST.get('uids', []) + if isinstance(uids, basestring): + uids = [uids] + for uid in uids: + Notification.mark_as_read(user.name, uid) + return HttpResponse(json.dumps(dict(r=0))) + else: + return HttpResponse(json.dumps(dict(r=1))) + + +def notification_read_all(request): + ''' mark all ''' + from vilya.models.notification import Notification + user = request.user + if user: + Notification.mark_all_as_read(user.name) + return HttpResponseRedirect('/hub/notification') + else: + return HttpResponseRedirect("/hub/teams") + + +@csrf_exempt +def notification_mute(request): + ''' mute ticket(pr) or issue, just 'project' scope yet. ''' + from vilya.models.mute import Mute + from vilya.models.project_issue import ProjectIssue + user = request.user + if user: + entry_type = request.POST.get('type', '') + target = request.POST.get('target', '') + entry_id = request.POST.get('id', '') + if entry_type == 'pull': + Mute.mute('ticket', target, entry_id, user) + elif entry_type == 'issue': + # TODO: models.issue.leave or mute + issue = ProjectIssue.get_by_proj_name_and_number(target, entry_id) + if user.name != issue.creator_id: + issue.delete_participant(user.name) + return HttpResponse(json.dumps(dict(r=0))) + else: + return HttpResponse(json.dumps(dict(r=1))) + + +def notification_notification(request, id): + from vilya.models.notification import Notification + user = request.user + if id: + Notification.mark_as_read(user.name, id) + url = request.GET.get('url') + # TODO fix request.query.url + return HttpResponseRedirect(url) + + +def stat_index(request): + return HttpResponse(st('stat.html', **locals())) + + +def stat_source(request): + from vilya.models.statistics import ( + get_all_ticket, + get_ticket_comment_count, + get_all_issue, + get_issue_comment_count, + get_all_project, + get_all_gist + ) + # pr相关数据 + pr_rs = get_all_ticket() + pr_count = len(pr_rs) + pr_open_count = len(filter(lambda x: x[1] is None, pr_rs)) + + # issue相关数据 + issue_rs = get_all_issue() + issue_count = len(issue_rs) + issue_open_count = len(filter(lambda x: x[1] is None, issue_rs)) + + project_rs = get_all_project() + gist_rs = get_all_gist() + + data = dict( + # pr相关数据 + pr_count=pr_count, + pr_open_count=pr_open_count, + pr_closed_count=pr_count - pr_open_count, + pr_comment_count=get_ticket_comment_count(), + # issue相关数据 + issue_count=issue_count, + issue_open_count=issue_open_count, + issue_closed_count=issue_count - issue_open_count, + issue_comment_count=get_issue_comment_count(), + # project相关数据 + project_count=len(project_rs), + project_fork_count=len(filter(lambda x: x[1] is not None, project_rs)), + # gist相关数据 + gist_count=len(gist_rs), + gist_fork_count=len(filter(lambda x: x[1] != 0, gist_rs)) + ) + + return HttpResponse(json.dumps(data)) diff --git a/vilya/views/django/hub_team.py b/vilya/views/django/hub_team.py new file mode 100644 index 0000000..960012a --- /dev/null +++ b/vilya/views/django/hub_team.py @@ -0,0 +1,384 @@ +# -*- coding: utf-8 -*- + +import re +import json +import requests +from django.http import Http404 +from django.http import HttpResponse +from django.http import HttpResponseRedirect +from django.views.decorators.csrf import csrf_exempt +from vilya.libs.template import st + + +def team_index(request, name): + from vilya.models.team import Team + team_uid = name + user = request.user + team = Team.get_by_uid(team_uid) + if not team: + raise Http404() + projects = team.projects + is_admin = False + if user and team.is_owner(user.name): + is_admin = True + return HttpResponse(st('/teams/team.html', **locals())) + + +def team_join(request, name): + from vilya.libs.signals import team_joined_signal + from vilya.models.consts import TEAM_MEMBER + from vilya.models.team import Team + user = request.user + team_uid = name + team = Team.get_by_uid(team_uid) + if not user or not team: + return HttpResponse(json.dumps(dict(r=1))) + + team.add_user(user, TEAM_MEMBER) + team_joined_signal.send(user.name, + team_id=team.id, + team_uid=team.uid, + team_name=team.name) + return HttpResponse(json.dumps(dict(r=0))) + + +@csrf_exempt +def team_upload_profile(request, name): + from vilya.models.consts import UPLOAD_URL + from vilya.models.team import Team + user = request.user + team_uid = name + team = Team.get_by_uid(team_uid) + if not user and not team: + return HttpResponse(json.dumps(dict(r=1))) + if team and not team.is_owner(user.name): + return HttpResponse(json.dumps(dict(r=1))) + + upload_url = request.POST.get('url', '') + hash_png = request.POST.get('hash', '') + profile = {'origin': upload_url} + if upload_url and hash_png: + # FIXME(xutao) remove useless UPLOAD_URL + rsize_url = '{0}/r/{1}?w=100&h=100'.format(UPLOAD_URL, hash_png) + r = requests.get(rsize_url) + r.raise_for_status() + profile.update({'icon': r.text}) + team.profile = profile + return HttpResponse(json.dumps(dict(r=0))) + +def team_leave(request, name): + from vilya.models.team import Team + user = request.user + team_uid = name + team = Team.get_by_uid(team_uid) + if not user or not team: + return HttpResponse(json.dumps(dict(r=1))) + + team.remove_user(user) + return HttpResponse(json.dumps(dict(r=0))) + + +@csrf_exempt +def team_add_project(request, name): + from vilya.models.project import CodeDoubanProject + from vilya.models.team import Team + user = request.user + team_uid = name + team = Team.get_by_uid(team_uid) + if not user and not team: + return HttpResponseRedirect('/') + if team and not team.is_owner(user.name): + return HttpResponseRedirect(team.url) + + project_name = request.POST.get('project_name') or '' + project = CodeDoubanProject.get_by_name(project_name) + error = '' + if request.method == 'POST': + if not project_name: + error = 'project_name_not_exists' + elif not project: + error = 'project_not_exists' + else: + team.add_project(project) + return HttpResponseRedirect(team.url) + return HttpResponse(st('/teams/team_add_project.html', **locals())) + + +@csrf_exempt +def team_remove_project(request, name): + from vilya.models.project import CodeDoubanProject + from vilya.models.team import Team + user = request.user + team_uid = name + team = Team.get_by_uid(team_uid) + if not user or not team: + return HttpResponse(json.dumps(dict(r=1))) + + if not team.is_owner(user.name): + return HttpResponse(json.dumps(dict(r=1))) + + project_name = request.POST.get('project_name', '') + project = CodeDoubanProject.get_by_name(project_name) + if not project: + return HttpResponse(json.dumps(dict(r=1))) + + team.remove_project(project) + return HttpResponse(json.dumps(dict(r=0))) + + +def team_add_user(request, name): + from vilya.libs.signals import team_add_member_signal + from vilya.models.user import User + from vilya.models.consts import TEAM_IDENTITY_INFO + from vilya.models.team import Team, TeamUserRelationship + user = request.user + team_uid = name + team = Team.get_by_uid(team_uid) + if not user or not team: + return HttpResponse(json.dumps(dict(r=1, error="team不存在"))) + + user_id = request.POST.get('user_id', '') + identity = int(request.POST.get('identity', 0)) + + if not team.is_owner(user.name) \ + or identity not in TEAM_IDENTITY_INFO.keys(): + return HttpResponse(json.dumps(dict(r=1, error="没有权限"))) + + rl = TeamUserRelationship.get(team_id=team.id, user_id=user_id) + if not rl: + team.add_user(User(user_id), identity) + elif identity == rl.identity: + return HttpResponse(json.dumps(dict(r=1, error="该用户已存在"))) + elif rl.is_owner and team.n_owners == 1: + return HttpResponse(json.dumps(dict(r=1, error="只剩一个creator, 不能改变身份"))) + else: + rl.identity = identity + rl.save() + + avatar_url = User(user_id).avatar_url + team_add_member_signal.send( + user.name, team_uid=team.uid, team_name=team.name, + receiver=user_id, identity=TEAM_IDENTITY_INFO[identity]["name"]) + return HttpResponse(json.dumps(dict(r=0, uid=user_id, avatar_url=avatar_url))) + + +@csrf_exempt +def team_remove_user(request, name): + from vilya.models.team import Team, TeamUserRelationship + team_uid = name + user = request.user + if not user: + return HttpResponse(json.dumps(dict(r=1, error="用户未登录"))) + + team = Team.get_by_uid(team_uid) + if not team: + return HttpResponse(json.dumps(dict(r=1, error="team不存在"))) + + user_id = request.POST.get('user_id', '') + + if not team.is_owner(user.name): + return HttpResponse(json.dumps(dict(r=1, error="没有权限"))) + rl = TeamUserRelationship.get(team_id=team.id, user_id=user_id) + if not rl: + return HttpResponse(json.dumps(dict(r=1, error="用户未加入team"))) + elif rl.is_owner and team.n_owners == 1: + return HttpResponse(json.dumps(dict(r=1, error="只剩一个creator不能删除"))) + else: + rl.delete() + return HttpResponse(json.dumps(dict(r=0))) + + +def team_remove(request, name): + from vilya.models.team import Team + team_uid = name + team = Team.get_by_uid(team_uid) + if not team: + return HttpResponse(json.dumps(dict(r=1))) + + user = request.user + if not user or not team.is_owner(user.name): + return HttpResponse(json.dumps(dict(r=1))) + + team.delete() + return HttpResponse(json.dumps({'r': 0})) + + +def team_news(request, name): + from vilya.models.feed import get_team_feed, PAGE_ACTIONS_COUNT + from vilya.models.team import Team + user = request.user + team_uid = name + team = Team.get_by_uid(team_uid) + if not team: + raise Http404() + feed = get_team_feed(team.id) + actions = feed.get_actions(stop=PAGE_ACTIONS_COUNT - 1) + projects = team.projects + is_admin = False + if user and team.is_owner(user.name): + is_admin = True + return HttpResponse(st('/teams/news.html', **locals())) + + +@csrf_exempt +def team_settings(request, name): + from vilya.models.team import Team + user = request.user + team_uid = name + team = Team.get_by_uid(team_uid) + if not team: + raise Http404() + projects = team.projects + + input_uid = request.POST.get('uid', '') + input_name = request.POST.get('name', '') + input_description = request.POST.get('description', '') + + error = '' + if request.method == "POST": + if not user: + return HttpResponseRedirect("/") + + if not team.is_owner(user.name): + return HttpResponseRedirect(team.url) + + teams = Team.gets() + team_uid_pattern = re.compile(r'[a-zA-Z0-9\_]*') + if not input_uid: + error = 'uid_not_exists' + elif not input_name: + error = 'name_not_exists' + elif input_uid != re.findall(team_uid_pattern, input_uid)[0]: + error = 'invilid_uid' + elif input_uid in [t.uid for t in teams] and team.uid != input_uid: + error = 'uid_existed' + elif input_name in [t.name for t in teams] \ + and team.name != input_name: + error = 'name_existed' + else: + team.update(input_uid, input_name, input_description) + return HttpResponseRedirect("/hub/team/%s/settings" % input_uid) + return HttpResponse(st('/teams/team_settings.html', **locals())) + + +def team_pulls(request, name): + from vilya.models.team import Team + from vilya.models.ticket import Ticket + TICKETS_COUNT_PER_PAGE = 30 + team_name = name + team = Team.get_by_uid(team_name) + user = request.user + page = request.GET.get('page', 1) + tickets = Ticket.gets_by_team_id( + team.id, limit=TICKETS_COUNT_PER_PAGE, + start=TICKETS_COUNT_PER_PAGE * (int(page) - 1)) or [] + ticket_total_len = Ticket.get_count_by_team_id(team.id) or 0 + is_closed_tab = False + n_pages = (ticket_total_len - 1) / TICKETS_COUNT_PER_PAGE + 1 + return HttpResponse(st('/teams/team_pulls.html', **locals())) + + +def team_pulls_closed(request, name): + from vilya.models.team import Team + from vilya.models.ticket import Ticket + TICKETS_COUNT_PER_PAGE = 30 + team_name = name + team = Team.get_by_uid(team_name) + user = request.user + page = request.GET.get('page', 1) + tickets = Ticket.gets_by_team_id( + team.id, closed=True, limit=TICKETS_COUNT_PER_PAGE, + start=TICKETS_COUNT_PER_PAGE * (int(page) - 1)) or [] + ticket_total_len = Ticket.get_count_by_team_id(team.id, + closed=True) or 0 + n_pages = (ticket_total_len - 1) / TICKETS_COUNT_PER_PAGE + 1 + is_closed_tab = True + return HttpResponse(st('/teams/team_pulls.html', **locals())) + + +def team_issues(request, name): + from vilya.models.team_issue import TeamIssue + from vilya.models.team import Team + from vilya.views.django.project_issue import get_order_type + from vilya.models.tag import Tag, TAG_TYPE_TEAM_ISSUE + cls = TeamIssue + team_uid = name + team = Team.get_by_uid(team_uid) + user = request.user + page = request.GET.get('page', 1) + state = request.GET.get("state", "open") + response = HttpResponse() + order = get_order_type(request, response, 'team_issues_order') + + team_issues = [] + + selected_tag_names = request.GET.get('tags', '') + if selected_tag_names: + selected_tag_names = selected_tag_names.split(',') + issue_ids = Tag.get_type_ids_by_names_and_target_id( + TAG_TYPE_TEAM_ISSUE, + selected_tag_names, + team.id) + team_issues = cls.gets_by_issue_ids(issue_ids, state) + else: + team_issues = cls.gets_by_target(team.id, state, order=order) + + n_team_issue = len(team_issues) + show_tags = team.get_group_tags(selected_tag_names) + is_closed_tab = None if state == "open" else True + n_pages = 1 + # TODO: 分页 + response.content = st('issue/team_issues.html', **locals()) + return response + + +def team_issues_new(request, name): + from vilya.models.team import Team + team_uid = name + team = Team.get_by_uid(team_uid) + user = request.user + current_user = request.user + tags = team.tags + error = request.GET.get('error') + teams = Team.get_all_team_uids() + return HttpResponse(st('issue/new_team_issue.html', **locals())) + + +@csrf_exempt +def team_issues_create(request, name): + from dispatches import dispatch + from vilya.models.team import Team + from vilya.models.team_issue import TeamIssue + from vilya.libs.signals import issue_signal + cls = TeamIssue + team_uid = name + team = Team.get_by_uid(team_uid) + if request.method == 'POST': + user = request.user + if not user: + raise Http404() + if not team: + raise Http404() + title = request.POST.get('title', '').decode('utf-8') + description = request.POST.get('body', '').decode('utf-8') + tags = request.POST.get('issue_tags', []) + if isinstance(tags, list): + tags = [tag.decode('utf-8') for tag in tags if tag] + elif isinstance(tags, basestring): + tags = [tags.decode('utf-8')] + + if not(title and description): + return HttpResponseRedirect('../new?error=empty') + + tissue = cls.add(title, description, user.name, team=team.id) + tissue.add_tags(tags, tissue.team_id) + # TODO: 重构feed后删除这个signal + issue_signal.send(author=user.name, content=description, + issue_id=tissue.issue_id) + dispatch('issue', data={ + 'sender': user.name, + 'content': description, + 'issue': tissue, + }) + return HttpResponseRedirect(tissue.url) + return HttpResponseRedirect(team.url + 'issues') diff --git a/vilya/views/django/issue.py b/vilya/views/django/issue.py new file mode 100644 index 0000000..f3391c1 --- /dev/null +++ b/vilya/views/django/issue.py @@ -0,0 +1,229 @@ +# -*- coding: utf-8 -*- + +import json +from django.http import Http404 +from django.http import HttpResponse +from django.http import HttpResponseForbidden +from django.http import HttpResponseRedirect +from django.views.decorators.csrf import csrf_exempt +from vilya.libs.template import st + + +class IssueBase(object): + + def __init__(self, request, target, issue, number, template): + self.request = request + self.target = target + self.issue = issue + self.issue_id = issue.issue_id + self.issue_number = number + self.issue_template = template + + +class IssueView(IssueBase): + + def index(self): + from vilya.models.team import Team + request = self.request + issue = self.issue + target = self.target + issue_template = self.issue_template + + show_close = True + user = request.user + current_user = request.user + author = issue.creator + i_am_author = user and issue.creator_id == user.username + i_am_admin = user and target.is_admin(user.name) + has_user_voted = issue.has_user_voted(user.name) if user else False + vote_count = issue.vote_count + teams = Team.get_all_team_uids() + return HttpResponse(st(issue_template, **locals())) + + + def upvote(self): + issue = self.issue + request = self.request + + def upvote_action(issue, user, request): + msg = '' + count = None + action = 0 + if not user: + msg = "You should login before voting on any issue!" + elif issue.is_closed: + msg = "You can not vote or unvote a closed issue!" + elif issue.creator_id == user.name: + msg = "You can not vote on issues created by yourself!" + elif request.method == 'PUT': + count = issue.upvote_by_user(user.name) + action = 1 + elif request.method == 'DELETE': + count = issue.cancel_upvote_by_user(user.name) + action = -1 + else: + action = 0 + msg = 'Unsupport Method' + + if count is None: + count = issue.vote_count + + return HttpResponse(json.dumps({ + 'action': action, + 'count': count, + 'msg': msg, + })) + user = request.user + return upvote_action(issue, user, request) + + def comment(self): + from dispatches import dispatch + from vilya.libs.signals import issue_signal, issue_comment_signal + from vilya.models.team import Team + request = self.request + issue = self.issue + issue_id = self.issue_id + target = self.target + + if request.method == 'POST': + content = request.POST.get('content', '').decode('utf-8') + user = request.user + user = user.name if user else None + if user: + author = user + if content.strip(): + comment = issue.add_comment(content, user) + issue.add_participant(user) + html = st('/widgets/issue/issue_comment.html', **locals()) + else: + return HttpResponse(json.dumps({'error': 'Content is empty'})) + + if request.POST.get('comment_and_close'): + issue.close(author) + # TODO: 重构feed后取消信号发送 + issue_signal.send(author=author, + content=content, + issue_id=issue_id) + dispatch('issue', data={ + 'sender': author, + 'content': content, + 'issue': issue, + }) + return HttpResponse(json.dumps(dict(r=0, reload=1, redirect_to=issue.url))) + elif request.POST.get('comment_and_open'): + issue.open() + # TODO: 重构feed后取消信号发送 + issue_signal.send(author=author, content=content, + issue_id=issue_id) + dispatch('issue', data={ + 'sender': author, + 'content': content, + 'issue': issue, + }) + return HttpResponse(json.dumps(dict(r=0, reload=1, redirect_to=issue.url))) + elif content: + issue_comment_signal.send(author=author, + content=comment.content, + issue_id=comment.issue_id, + comment_id=comment.id) + dispatch('issue_comment', data={ + 'sender': author, + 'content': comment.content, + 'issue': issue, + 'comment': comment}) + participants = issue.participants + + teams = Team.get_all_team_uids() + + participants_html = st('/widgets/participation.html', + **locals()) # FIXME: locals()? + return HttpResponse(json.dumps(dict( + r=0, html=html, participants_html=participants_html))) + return HttpResponseRedirect(issue.url) + + def tag(self): + request = self.request + issue = self.issue + target = self.target + + def split_tags_str(tags): + if not tags: + return [] + tags = tags.split() + return tags + + if request.method == 'POST': + user = request.user + user = user.name if user else None + if user: + tags = request.POST.getlist('tags', []) + if isinstance(tags, basestring): + tags = split_tags_str(tags.decode('utf8')) + tags_orig = [tag.name for tag in issue.tags] + tags_to_add = list(set(tags).difference(tags_orig)) + tags_to_del = list(set(tags_orig).difference(tags)) + issue.add_tags(tags_to_add, target.id, user) + issue.remove_tags(tags_to_del, target.id) + return HttpResponseRedirect(issue.url) + + def milestone(self): + request = self.request + issue = self.issue + + if request.method == 'POST': + user = request.user + if not user: + return HttpResponseRedirect(issue.url) + milestone = request.POST.get('milestone', '') + milestone_title = request.POST.get('milestone_title', '') + if milestone == 'new' and milestone_title: + issue.add_milestone(user, name=milestone_title) + elif milestone == 'clear': + issue.remove_milestone() + else: + issue.add_milestone(user, milestone_id=milestone) + return HttpResponseRedirect(issue.url) + + def join(self): + from vilya.models.team import Team + request = self.request + issue = self.issue + target = self.target # noqa for template + + user = request.user + if user: + issue.add_participant(user.name) + participants = issue.participants + teams = Team.get_all_team_uids() + participants_html = st('/widgets/participation.html', + participants=participants, teams=teams) + return HttpResponse(json.dumps(dict(r=0, participants_html=participants_html))) + return HttpResponse(json.dumps(dict(r=1))) + + def leave(self): + from vilya.models.team import Team + request = self.request + issue = self.issue + + user = request.user + if user: + if user.name == issue.creator_id: + return HttpResponse(json.dumps(dict(r=1, msg="Can't leave the issue created by you."))) + issue.delete_participant(user.name) + participants = issue.participants + teams = Team.get_all_team_uids() + participants_html = st('/widgets/participation.html', + participants=participants, teams=teams) + return HttpResponse(json.dumps(dict(r=0, participants_html=participants_html))) + return HttpResponse(json.dumps(dict(r=1))) + + def assign(self): + request = self.request + issue = self.issue + + if request.method == 'POST': + user = request.user + if user: + assignee = request.POST.get('assignee', '').decode('utf-8') + issue.assign(assignee) + return HttpResponseRedirect(issue.url) diff --git a/vilya/views/django/project.py b/vilya/views/django/project.py new file mode 100644 index 0000000..4c1d874 --- /dev/null +++ b/vilya/views/django/project.py @@ -0,0 +1,757 @@ +# -*- coding: utf-8 -*- + +import re +import os +import json +import urllib +from django.http import Http404 +from django.http import HttpResponse +from django.http import HttpResponseRedirect +from django.http import HttpResponseBadRequest +from django.http import StreamingHttpResponse +from django.utils.decorators import method_decorator +from django.views.decorators.csrf import csrf_exempt +from django.views.generic import View +from vilya.libs.template import st +from vilya.models.consts import TEMP_BRANCH_MARKER + + +def _latest_update_branch(project, ref, user): + from vilya.models.pull import PullRequest + if user: + get_pr = user.get_user_submit_pull_requests + latest_tickets = get_pr(limit=5, is_closed=False) + get_pr( + limit=5, is_closed=True) + latest_tickets = filter(None, [PullRequest.get_by_ticket(t) + for t in latest_tickets if t.project]) + has_pr_branches = [t.from_ref for t in latest_tickets] + else: + has_pr_branches = [] + latest_update_branches = filter(lambda b: b[1] != ref + and b[1] not in has_pr_branches, + project.repo.get_latest_update_branches()) + latest_update_branch = (latest_update_branches[0][1] + if latest_update_branches else '') + return latest_update_branch + + +def watch_index(request): + from vilya.models.project import CodeDoubanProject + """get all watches""" + user = request.user + if user: + return HttpResponse(json.dumps([ + {'pid': str(p.id)} + for p in CodeDoubanProject.get_watched_projects_by_user(user.name)])) # noqa + return HttpResponse('') + + +@csrf_exempt +def watch(request, id): + from vilya.models.project import CodeDoubanProject + from vilya.views.util import error_message + + def new(request, proj_id): + user = request.user + CodeDoubanProject.add_watch(proj_id, user.name) + return HttpResponse(json.dumps({"ok": 1})) + + + def remove(request, proj_id): + user = request.user + CodeDoubanProject.del_watch(proj_id, user.name) + return HttpResponse(json.dumps({"ok": 1})) + + + # FIXME: ugly fix + def has_watched(request, proj_id): + user = request.user + if CodeDoubanProject.has_watched(proj_id, user.name): + return HttpResponse(json.dumps({"ok": 1})) + return HttpResponse(json.dumps({"ok": 0})) + + proj_id = id + if request.method == "POST": + return new(request, proj_id) + elif request.method == "DELETE": + return remove(request, proj_id) + elif request.method == "GET": + return has_watched(request, proj_id) + else: + return HttpResponse(error_message("bad request")) + + +@csrf_exempt +def fetch(request, id): + from vilya.views.util import error_message + from tasks import fetch_mirror_project + if request.method == "POST": + fetch_mirror_project(id) + return HttpResponse(json.dumps({"ok": 1})) + return HttpResponse(error_message("bad request")) + + +def watchers(request, username, projectname): + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + project = CodeDoubanProject.get_by_name(name) + projects = [] + user = request.user + users = project.get_watch_users() + return HttpResponse(st('watchers.html', **locals())) + + +def forkers(request, username, projectname): + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + project = CodeDoubanProject.get_by_name(name) + projects = project.get_forked_projects() + user = request.user + users = project.get_forked_users() + return HttpResponse(st('watchers.html', **locals())) + + +def archive(request, username, projectname, revision): + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + part = revision + project = CodeDoubanProject.get_by_name(name) + repo = project.repo + sha = repo.sha(revision) + if not sha: + return HttpResponseBadRequest() + + ext = request.GET.get('ext') + if ext == 'tar': + response = StreamingHttpResponse(content_type='application/x-tar') + response['Content-Disposition'] = "filename=%s.tar" % part + response.streaming_content = repo.archive(part, ref=sha, ext=ext) + elif ext == 'tar.gz': + response = StreamingHttpResponse(content_type='application/x-gzip') + response['Content-Disposition'] = "filename=%s.tar.gz" % part + response.streaming_content = repo.archive(part, ref=sha) + else: + response = StreamingHttpResponse(content_type='application/x-gzip') + response['Content-Disposition'] = "filename=%s.tar.gz" % part + response.streaming_content = repo.archive(part, ref=sha) + return response + + +class ProjectView(View): + + template_name = '' + + def get(self, request, *args, **kwargs): + tdt = self.get_data(request, *args, **kwargs) + return HttpResponse(st(self.template_name, **tdt)) + + def get_common_data(self, request, name, revision, path): + from vilya.models.project import CodeDoubanProject + user = request.user + project = CodeDoubanProject.get_by_name(name) + + ref = revision + if ref is None: + ref = project.default_branch + + branches = project.repo.branches + tags = project.repo.tags + ref_type = 'tree' + if ref in branches: + ref_type = 'branch' + elif ref in tags: + ref_type = 'tag' + + blob_path = path.decode('utf-8') + + def _get_breadcrumb(path): + subpath = [] + + def get_subpath(prev_path, path): + if path: + path = path.strip('/') + prev_path = prev_path.strip('/') + cur, sep, next = path.partition('/') + if prev_path: + cur_path = '%s/%s' % (prev_path, cur) + else: + cur_path = cur + subpath.append({'title': cur.decode( + 'utf-8'), 'path': cur_path.decode('utf-8')}) + get_subpath(cur_path, next) + + get_subpath('', path) + return subpath + + path_breadcrumb = _get_breadcrumb(path) + + latest_branch = _latest_update_branch(project, ref, user) + if latest_branch: + latest_update_branch = [(project, latest_branch, latest_branch)] + else: + latest_update_branch = [] + + tdt = { + 'errors': '', + 'branches': branches, + 'latest_update_branch': latest_update_branch, + 'rev': revision, + 'tags': tags, + 'blob_path': blob_path, + 'file_name': blob_path.rpartition('/')[-1], + 'request': request, + 'project': project, + 'project_name': name, + 'path': path, + 'path_breadcrumb': path_breadcrumb, + 'ref_type': ref_type, + 'blob_ref': ref, + 'ref': ref, + 'user': user, + } + return tdt + + +class ProjectBlobView(ProjectView): + template_name = 'blob.html' + + def get_data(self, request, username, projectname, revision, path=''): + from vilya.models.project import CodeDoubanProject + ''' get blob view ''' + name = '/'.join([username, projectname]) + project = CodeDoubanProject.get_by_name(name) + path = path.encode('utf-8') + tdt = self.get_common_data(request, name, revision, path) + ref = revision + if ref is None: + ref = project.default_branch + last_commit = project.repo.get_last_commit( + ref, path=path, no_merges=True) if ref and path else '' + tdt.update({ + 'lastcommit': last_commit, + }) + return tdt + + +@method_decorator(csrf_exempt, name="dispatch") +class ProjectEditView(ProjectView): + template_name = 'edit.html' + + def post(self, request, username, projectname, revision, path=''): + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + project = CodeDoubanProject.get_by_name(name) + path = path.encode('utf-8') + rgf = request.POST.get + errors = '' + success = '' + ref = revision + user = request.user + direct_edit_allowed = True + if not user: + direct_edit_allowed = False + elif not project.has_push_perm(user.name): + direct_edit_allowed = False + if ref is None: + ref = project.default_branch + source = rgf('code') + message = rgf('message') + desc = rgf('desc') + if message == '': + errors = u'commit summary不能为空' + if rgf('newfile') and not re.match('^[a-zA-Z_\-0-9.]+$', rgf('newfilename', '')): + errors = 'new filename incorrect: (%s)' % rgf('newfilename') + if not errors: + if rgf('newfile'): + commit_fn = os.path.join(path, rgf('newfilename')) + assert not project.repo.get_commit( + "%s:%s" % (ref, commit_fn)), "file_already_exist" + else: + commit_fn = rgf('filename') + message = "%s\n\n%s" % (message, desc) + message = message.decode("utf8") + data = [] + data.append([commit_fn, source, 'insert']) + if direct_edit_allowed: + reflog = 'commit_one_file on %s' % ref + assert ref == 'master' or ref in project.repo.branches, \ + "commit online allowed on existing branches only" + try: + project.repo.commit_file( + ref, ref, user.name, user.email, message, reflog, data) + success = u'提交成功' + except AssertionError as err: + errors = 'Error updating file %s: REPO COULD BE CORRUPTED, FETCH IT MANUALLY AND CHECK' % err # noqa + redir = str('/%s/blob/%s/%s' % (name, revision, commit_fn)) + return HttpResponseRedirect(redir) + else: + reflog = TEMP_BRANCH_MARKER + tmp_branch = project.repo.get_temp_branch() + project.repo.commit_file( + tmp_branch, ref, user.name, user.email, message, + reflog, data) + redir = '/%s/newpull/new?base_repo=%s&head_ref=%s' % ( + name, name, urllib.quote(tmp_branch)) + return HttpResponseRedirect(redir) + + def get_data(self, request, username, projectname, revision, path=''): + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + project = CodeDoubanProject.get_by_name(name) + path = path.encode('utf-8') + rgf = request.POST.get + tdt = self.get_common_data(request, name, revision, path) + errors = '' + success = '' + ref = revision + user = request.user + direct_edit_allowed = True + if not user: + direct_edit_allowed = False + elif not project.has_push_perm(user.name): + direct_edit_allowed = False + if ref is None: + ref = project.default_branch + # FIXME: 跟本文件中的其他 get_last_commit 调用参数不一致? + last_commit = project.repo.get_last_commit(ref) + + _help_links = { + '.rst': 'http://code.dapps.douban.com/metadoc/docs/', + '.md': 'http://daringfireball.net/projects/markdown/', + '.py': 'http://docs.python.org/2.7/', + '.html': 'http://docs.makotemplates.org/en/latest/', + '.css': 'http://www.w3.org/Style/CSS/Overview.en.html', + '.js': 'https://developer.mozilla.org/en/docs/JavaScript', + '.yaml': 'http://www.yaml.org/refcard.html', + 'default': 'http://www.wikipedia.org/' + } + + def _get_help_link(path): + ext = os.path.splitext(path)[1] + return _help_links.get(ext, _help_links['default']) + + tdt.update({ + 'success': success, + 'errors': errors, + 'direct_edit_allowed': direct_edit_allowed, + 'help_link': _get_help_link(path), + 'lastcommit': last_commit, + }) + if rgf('newfile'): + tdt.update({ + 'newfile': True, + 'newfilename': rgf('newfilename', ''), + 'text': rgf('code', ''), + 'template_text': rgf('template_code', '').replace(r'\n', '\n'), + 'orig_hash': False, + }) + else: + try: + text = project.repo.get_file_by_ref( + "%s:%s" % (ref, path.decode('utf-8'))) + except IOError: + raise Http404() + tdt.update({ + 'text': text.decode('utf8'), + 'newfile': False, + 'template_text': rgf('template_code', '').replace(r'\n', '\n'), + 'orig_hash': hash(text), + }) + return tdt + + +class ProjectBlameView(ProjectView): + template_name = 'blame.html' + + def get_data(self, request, username, projectname, revision, path=''): + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + project = CodeDoubanProject.get_by_name(name) + tdt = self.get_common_data(request, name, revision, path) + ref = revision + if ref is None: + ref = project.default_branch + blob_path = path.decode('utf-8') + try: + blame = project.repo.blame_file(ref, blob_path) + commit = project.repo.get_commit(ref) + except IOError: + raise Http404() + tdt.update({ + 'blame': blame, + 'commit': commit, + }) + return tdt + + +class ProjectRawView(ProjectView): + template_name = 'patch.html' + + def get(self, request, username, projectname, revision, path=''): + from vilya.libs.text import is_image, is_binary + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + project = CodeDoubanProject.get_by_name(name) + rev = revision + if rev is None: + rev = project.default_branch + try: + blob = project.repo.get_file(rev, path.decode('utf-8')) + except IOError: + raise Http404() + if not blob: + raise Http404("No content found") + + response = StreamingHttpResponse() + if is_image(path): + if path.endswith('svg'): + response['Content-Type'] = "image/svg+xml" + else: + response['Content-Type'] = "image/jpeg" + response['Expires'] = "Sun, 1 Jan 2006 01:00:00 GMT" + response['Pragma'] = "no-cache" + response['Cache-Control'] = "must-revalidate, no-cache, private" + response.streaming_content = blob.data + return response + if path.endswith('.pdf'): + response['Content-Type'] = "application/pdf" + response.streaming_content = blob.data + return response + if is_binary(path): + response['Content-Type'] = "application/octet-stream" + response['Content-Disposition'] = "attachment;filename=%s" % path.split('/')[-1] + response['Content-Transfer-Encoding'] = "binary" + response.streaming_content = blob.data + return response + + response['Content-Type'] = "text/plain;charset=utf-8" + response.streaming_content = blob.data.encode('utf8') + return response + + +class ProjectTreeView(ProjectView): + template_name = 'tree.html' + + def get_data(self, request, username, projectname, revision, path=''): + from vilya.libs.text import format_md_or_rst + from vilya.models.mirror import CodeDoubanMirror + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + project = CodeDoubanProject.get_by_name(name) + if not project: + raise Http404("Wrong path for tree %s" % path) + if project.is_mirror_project: + mirror = CodeDoubanMirror.get_by_project_id(project.id) + if not mirror.is_clone_completed: + return st('/projects/mirror_cloning.html', **locals()) + if not project.repo: + raise Http404("Wrong path for tree %s" % path) + ref = revision + if not ref: + ref = project.default_branch + + tree_path = path.decode('utf-8') + last_commit = project.repo.get_last_commit(ref, path=path) if ref else '' + tdt = self.get_common_data(request, name, ref, path) + user = tdt['user'] + if user is not None: + username = user.username + cur_user_project = project.get_forked_project(username) + if cur_user_project is not None: + latest_branch = _latest_update_branch( + cur_user_project, ref, user) + if latest_branch: + cur_user_latest_branch_name = '{0}:{1}'.format( + username, latest_branch) + tdt['latest_update_branch'].append( + (cur_user_project, cur_user_latest_branch_name, + latest_branch)) + tdt.update({ + 'lastcommit': last_commit, + }) + + tree = [] + is_empty = True + if last_commit: + tree = project.repo.get_tree(ref, path) + is_empty = False if tree else True + + if is_empty and not project.repo.is_empty: + raise Http404("Wrong path for tree %s" % path) + + if isinstance(tree, basestring): + raise Http404("Got a blob instead of a tree") + + # Add README code to tree if any + for item in tree: + if (item['type'] == 'blob' + and (item['name'] == 'README' + or item['name'].startswith('README.'))): + readme_content = project.repo.get_file_by_ref( + "%s:%s" % (ref, item['path']) + ) + tdt.update({ + 'readme_content': format_md_or_rst(item['path'], + readme_content, + project.name), + }) + break + + tdt.update({ + 'tree': tree, + 'tree_path': tree_path, + 'is_empty': is_empty, + 'is_go_get': request.GET.get('go-get', 0) + }) + return tdt + + +class ProjectCommitsView(ProjectView): + template_name = 'commits.html' + + def get_data(self, request, username, projectname, revision, path=''): + from vilya.models.release import get_release + from vilya.models.ngit.commits_graph import generate_graph_data + from vilya.models.comment import Comment + from vilya.models.project import CodeDoubanProject + NB_COMMITS_PER_PAGE = 20 + name = '/'.join([username, projectname]) + project = CodeDoubanProject.get_by_name(name) + path = path.encode('utf-8') + show_merges = request.GET.get('show_merges', None) + if show_merges and show_merges.isdigit(): + show_merges = int(show_merges) + else: + show_merges = 0 if path else 1 + + # Keep start_rev for older links + start_rev = request.GET.get('start_rev', None) + if start_rev and not revision: + revision = start_rev + if not revision: + revision = project.default_branch + page = int(request.GET.get('page', 1)) + author = request.GET.get('author', None) + query = request.GET.get('query', None) + skip = NB_COMMITS_PER_PAGE * (page - 1) + tdt = self.get_common_data(request, name, revision, path) + revlist = project.repo.get_commits(revision, max_count=NB_COMMITS_PER_PAGE, + skip=skip, path=path, author=author, + query=query, + no_merges=(not show_merges)) + + def _get_comment_counts(revlist, proj_id): + def _pack(sha): + return [(c.author, c.short_content) + for c in Comment.gets_by_proj_and_ref(proj_id, sha)] + return dict((rev.sha, _pack(rev.sha)) for rev in revlist) + + comment_counts = _get_comment_counts(revlist, project.id) + + # handle renamed file + renames = dict() + if revlist: # and not next: + oldcommit = revlist[-1] + renames = project.repo.get_renamed_files(oldcommit.sha) + rename_from = renames.get(path, '') + + graph_data = generate_graph_data(revlist) + older_revlist = project.repo.get_commits(revision, + max_count=NB_COMMITS_PER_PAGE, + skip=(NB_COMMITS_PER_PAGE * page), + path=path, author=author, + query=query) + + release = get_release(project.repository) + + def _make_links(project, rev, author, path, page, query): + if page < 1: + return False + params = [] + if author: + params.append(('author', author)) + if page and page != 1: + params.append(('page', page)) + if query: + params.append(('query', query)) + querystring = urllib.urlencode(params) + querystring = '?' + querystring if querystring else '' + return "/{project.name}/commits/{rev}/{path}{querystring}".format( + project=project, rev=rev, path=path, querystring=querystring + ) + + tdt.update({ + 'comment_counts': comment_counts, + 'rename_from': rename_from, + 'author': author, + 'query': query, + 'revlist': revlist, + 'renames': renames, + 'page': page, + 'link_prev': _make_links(project, revision, author, path, page - 1, query), + 'link_next': _make_links(project, revision, author, path, page + 1, query) + if older_revlist else False, + 'release': release, + 'graph_data': graph_data, + }) + return tdt + + +def browsefiles(request, username, projectname): + from vilya.models.project import CodeDoubanProject + + def _add_file_type_and_warns(node): + code_file_exts = 'py rb c h html mako ptl js css less handlebars coffee sql'.split() # noqa + bad_exts = 'pyc exe'.split() + node_ext = node['path'].rsplit('.')[1] if '.' in node['path'] else '' + if node['type'] == 'tree': + icon_type = 'directory' + elif node['type'] == 'commit': + icon_type = 'submodule' + elif node_ext in code_file_exts: + icon_type = 'code-file' + else: + icon_type = 'text-file' + node['icon-type'] = icon_type + if node_ext in bad_exts: + node['warn'] = 'bad' + else: + node['warn'] = 'no' + return node + + name = '/'.join([username, projectname]) + + if 'json' in request.environ['HTTP_ACCEPT']: + output = 'json' + else: + output = 'html' + project = CodeDoubanProject.get_by_name(name) + user = request.user + path = request.GET.get('path', '') + rev = request.GET.get('rev', project.default_branch) + allfiles = project.repo.get_tree(rev, path=path) + allfiles = [_add_file_type_and_warns(f) for f in allfiles] + errors = '' + ref = rev + if ref is None: + ref = project.default_branch + branches = project.repo.branches + tags = project.repo.tags + ref_type = 'branch' if ref in branches else 'tag' \ + if ref in tags else 'tree' + if output == 'json': + return HttpResponse(json.dumps(allfiles)) + else: + return HttpResponse(st('browsefiles.html', **locals())) + + +@csrf_exempt +def codereview_delete(request, username, projectname, id): + from vilya.models.linecomment import PullLineComment + comment = PullLineComment.get(id) + if not comment: + raise Http404("Unable to find comment %s" % id) + + user = request.user + if comment.author == user.name: + ok = comment.delete() + if ok: + return HttpResponse(json.dumps({'r': 1})) # FIXME: 这里 r=1 表示成功,跟其他地方不统一 + return HttpResponse(json.dumps({'r': 0})) + + +@csrf_exempt +def codereview_edit(request, username, projectname, id): + from vilya.models.linecomment import PullLineComment + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + comment = PullLineComment.get(id) + if not comment: + raise Http404("Unable to find comment %s" % id) + + user = request.user + project = CodeDoubanProject.get_by_name(name) + content = request.POST.get( + 'pull_request_review_comment', '').decode('utf-8') + if comment.author == user.name: + comment.update(content) + linecomment = PullLineComment.get(comment.id) + pullreq = True + return HttpResponse(json.dumps(dict( + r=0, html=st('/pull/ticket_linecomment.html', **locals())))) + return HttpResponse(json.dumps(dict(r=1))) + + +def comment(request, username, projectname): + from vilya.models.comment import latest + return HttpResponse("Last comments list TODO" + str(latest())) + + +@csrf_exempt +def comment_new(request, username, projectname): + from vilya.models.project import CodeDoubanProject + from vilya.models.comment import Comment + name = '/'.join([username, projectname]) + project = CodeDoubanProject.get_by_name(name) + user = request.user + ref = request.POST.get('ref') + assert ref, "comment ref cannot be empty" + content = request.POST.get('content', '') + new_comment = Comment.add(project, ref, user.name, content) + + return HttpResponseRedirect("/%s/commit/%s#%s" % + (name, ref, new_comment.uid)) + + +@csrf_exempt +def comment_delete(request, username, projectname, id): + from vilya.models.comment import Comment + if request.method == 'DELETE': + # FIXME: 不用验证user? + ok = Comment.delete(id) + if not ok: + raise Http404("Unable to delete comment %s" % id) + return HttpResponse('') + return HttpResponse("Display comment %s TODO" % id) + + +def compare_index(request, username, projectname): + return HttpResponseBadRequest('please provide valid start & end revisions: /compare/start...end') + + +def compare_range(request, username, projectname, range): + from itertools import groupby + from vilya.models.project import CodeDoubanProject + from vilya.models.comment import Comment + name = '/'.join([username, projectname]) + revrange = range + project = CodeDoubanProject.get_by_name(name) + current_user = request.user + try: + sha1, sha2 = revrange.split('...') + except ValueError: + raise Http404( + 'please provide valid start & end revisions: /compare/sha1...sha2') # noqa + commits = project.repo.get_commits(sha2, sha1) + if commits is False: + raise Http404() + lasttime = commits and commits[0].author_time.strftime( + "%Y-%m-%d %H:%M:%S") or 'UNKNOWN' + grouped_commits = groupby(commits, lambda c: c.author_time.date()) + n_commits = len(commits) + n_authors = len(set(c.author.username for c in commits)) + diff = project.repo.get_diff(sha2, + from_ref=sha1, + rename_detection=True) + #diffs = project.git.get_3dot_diff(sha1, sha2) + n_files = diff.length if diff else 0 + comments = [] + for ci in commits: + comments.extend(Comment.gets_by_proj_and_ref(project.id, ci.sha)) + branches = project.repo.branches + tags = project.repo.tags + ref = project.default_branch + n_comments = len(comments) + ref_type = 'branch' if ref in branches else 'tag' \ + if ref in tags else 'tree' + return HttpResponse(st('compare.html', **locals())) diff --git a/vilya/views/django/project_issue.py b/vilya/views/django/project_issue.py new file mode 100644 index 0000000..c6ccce6 --- /dev/null +++ b/vilya/views/django/project_issue.py @@ -0,0 +1,415 @@ +# -*- coding: utf-8 -*- + +import json +from django.http import Http404 +from django.http import HttpResponse +from django.http import HttpResponseForbidden +from django.http import HttpResponseRedirect +from django.views.decorators.csrf import csrf_exempt +from vilya.libs.template import st +from vilya.views.django.issue import IssueView + + +def _issues_filter(list_type): + ''' filter repo, created_by, assigned, search 4 list types. ''' + def index(request, username, projectname): + from vilya.models.elastic import SearchEngine + from vilya.models.elastic.issue_pr_search import IssueSearch + from vilya.models.project import CodeDoubanProject + from vilya.models.project_issue import ProjectIssue + from vilya.models.tag import TagName + from vilya.models.milestone import Milestone + ISSUES_COUNT_PER_PAGE = 25 + if list_type == 'search': + key_word = request.GET.get('q', None) + if not key_word: + return issues_index(request, username, projectname) + + _name = '/'.join([username, projectname]) + project = CodeDoubanProject.get_by_name(_name) + + milestone_number = request.GET.get('milestone') + state = request.GET.get('state', 'open') + page = request.GET.get('page', 1) + project_name = _name + user = request.user + response = HttpResponse() + order = get_order_type(request, response, 'project_issues_order') # noqa 目前支持list_type = repo的sort_by + n_open_issues = project.n_open_issues + n_closed_issues = project.n_closed_issues + n_everyone_issues = 0 + n_assigned_issues = 0 + n_created_issues = 0 + n_pages = 0 + selected_tag_names = request.GET.get('tags', '') + start = ISSUES_COUNT_PER_PAGE * (int(page) - 1) + limit = ISSUES_COUNT_PER_PAGE + + is_closed_tab = None if state == "open" else True + issue_list = [] + total_issues = 0 + opts = dict(project=project, state=state, start=start, + limit=limit, order=order) + if selected_tag_names: + selected_tag_names = selected_tag_names.split(',') + tags = filter(None, [TagName.get_project_issue_tag( + name, project) for name in selected_tag_names]) + opts['tags'] = tags + show_tags = project.get_group_tags(selected_tag_names) + + if milestone_number: + milestone = Milestone.get_by_project( + project, number=milestone_number) + opts['milestone'] = milestone + + # FIXME: why user or list_type ? + if user or list_type in ('repo', 'search'): + if list_type == 'search': + # FIXME: search with assigned or creator + search_result = IssueSearch.search_a_phrase( + key_word, project.id, + size=n_open_issues + n_closed_issues, + state=state) or [] + search_issue_ids = [] + if search_result and not search_result.get('error'): + search_issue_ids = [ + id for id, in SearchEngine.decode( + search_result, ['issue_id'])] + # FIXME: is search_issue_ids int[]? + opts['issue_ids'] = search_issue_ids + elif list_type == 'created_by': + opts['creator'] = user + elif list_type == 'assigned': + opts['assignee'] = user + # FIXME: update n_closed_issues & n_open_issues + multi_dict = ProjectIssue.get_multi_by(**opts) + issue_list = multi_dict['issues'] + total_issues = multi_dict['total'] + + if user: + if list_type == 'repo': + n_assigned_issues = user.get_n_assigned_issues_by_project(project.id, state) # noqa + n_created_issues = user.get_n_created_issues_by_project(project.id, state) # noqa + elif list_type == 'created_by': + n_assigned_issues = user.get_n_assigned_issues_by_project(project.id, state) # noqa + n_created_issues = total_issues + elif list_type == 'assigned': + n_assigned_issues = total_issues + n_created_issues = user.get_n_created_issues_by_project(project.id, state) # noqa + elif list_type == 'search' and search_issue_ids: + n_assigned_issues = ProjectIssue.get_n_by_issue_ids_and_assignee_id( # noqa + search_issue_ids, state, user.name) + n_created_issues = ProjectIssue.get_n_by_issue_ids_and_creator_id( # noqa + search_issue_ids, state, user.name) + n_pages = (total_issues - 1) / ISSUES_COUNT_PER_PAGE + 1 + + # tags 的选择只会改变选中的filter的显示issue数 + if list_type in ('repo', 'search'): + n_everyone_issues = total_issues + else: + n_everyone_issues = n_open_issues \ + if state == "open" else n_closed_issues + response.content = st('issue/issues.html', **locals()) + return response + return index + + +issues_index = _issues_filter('repo') +issues_created_by = _issues_filter('created_by') +issues_assigned = _issues_filter('assigned') +issues_search = _issues_filter('search') + + +def get_order_type(request, response, cookie_name): + ''' order cookie getter/setter ''' + cookie_order = request.COOKIES.get(cookie_name) + order = request.GET.get('order', cookie_order) + if order != 'hot': + order = 'date' + if order != cookie_order: + response.set_cookie(cookie_name, + order, + expires="Thu 01-Jan-2020 00:00:00 GMT") + return order + + +def issues_new(request, username, projectname): + from vilya.models.team import Team + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + project = CodeDoubanProject.get_by_name(name) + project_name = name + + user = request.user + tags = project.tags + error = request.GET.get('error') + teams = Team.get_all_team_uids() + return HttpResponse(st('issue/new.html', **locals())) + + +@csrf_exempt +def issues_create(request, username, projectname): + from dispatches import dispatch + from vilya.libs.signals import issue_signal + from vilya.models.project_issue import ProjectIssue + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + project = CodeDoubanProject.get_by_name(name) + project_name = name + + if request.method == 'POST': + user = request.user + if not user: + raise Http404() + project = request.POST.get('project') + title = request.POST.get('title', '').decode('utf-8') + description = request.POST.get('body', '').decode('utf-8') + tags = request.POST.get('issue_tags', []) + if isinstance(tags, list): + tags = [tag.decode('utf-8') for tag in tags if tag] + elif isinstance(tags, basestring): + tags = [tags.decode('utf-8')] + + if not project: + raise Http404() + if not title.strip(): + return HttpResponseRedirect('/%s/issues/new?error=empty' % project) + project = CodeDoubanProject.get_by_name(project) + pissue = ProjectIssue.add(title, description, user.name, + project=project.id) + pissue.add_tags(tags, pissue.project_id) + # TODO: 重构feed后取消信号发送 + issue_signal.send(author=user.name, content=description, + issue_id=pissue.issue_id) + dispatch('issue', data={ + 'sender': user.name, + 'content': description, + 'issue': pissue + }) + return HttpResponseRedirect(pissue.url) + return HttpResponseRedirect('/%s/issues' % project_name) + + +def issues_metioned(request, username, projectname): + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + project = CodeDoubanProject.get_by_name(name) + + list_type = 'mentioned' + state = request.GET.get('state', 'open') + page = request.GET.get('page', 1) + project_name = name + user = request.user + n_open_issues = project.n_open_issues + n_closed_issues = project.n_closed_issues + n_everyone_issues = 0 + n_assigned_issues = 0 + n_created_issues = 0 + n_mentioned_issues = 0 + n_pages = 0 + issue_list = [] + total_issues = 0 + is_closed_tab = None if state == "open" else True + if user: + n_assigned_issues = user.get_n_assigned_issues_by_project(project.id, state) # noqa + n_created_issues = user.get_n_created_issues_by_project(project.id, state) # noqa + n_everyone_issues = n_open_issues \ + if state == "open" else n_closed_issues + return HttpResponse(st('issue/issues.html', **locals())) + + +def issues_milestones(request, username, projectname): + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + project = CodeDoubanProject.get_by_name(name) + milestones = project.issue_milestones + return HttpResponse(st('/issue/milestones.html', **locals())) + + +def issue(request, username, projectname, id): + from vilya.models.issue import Issue + from vilya.models.project_issue import ProjectIssue + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + target = CodeDoubanProject.get_by_name(name) + issue_number = id + project_issue = ProjectIssue.get(target.id, + number=issue_number) + issue_id = project_issue.issue_id + issue = Issue.get_cached_issue(issue_id) + issue_template = 'issue/issue.html' + + return IssueView(request, target, issue, id, issue_template).index() + + +@csrf_exempt +def issue_upvote(request, username, projectname, id): + from vilya.models.issue import Issue + from vilya.models.project_issue import ProjectIssue + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + target = CodeDoubanProject.get_by_name(name) + issue_number = id + project_issue = ProjectIssue.get(target.id, + number=issue_number) + issue_id = project_issue.issue_id + issue = Issue.get_cached_issue(issue_id) + + return IssueView(request, target, issue, id, '').upvote() + + +@csrf_exempt +def issue_comment(request, username, projectname, id): + from vilya.models.issue import Issue + from vilya.models.project_issue import ProjectIssue + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + target = CodeDoubanProject.get_by_name(name) + issue_number = id + project_issue = ProjectIssue.get(target.id, + number=issue_number) + issue_id = project_issue.issue_id + issue = Issue.get_cached_issue(issue_id) + return IssueView(request, target, issue, id, '').comment() + + + +@csrf_exempt +def issue_tag(request, username, projectname, id): + from vilya.models.issue import Issue + from vilya.models.project_issue import ProjectIssue + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + target = CodeDoubanProject.get_by_name(name) + issue_number = id + project_issue = ProjectIssue.get(target.id, + number=issue_number) + issue_id = project_issue.issue_id + issue = Issue.get_cached_issue(issue_id) + return IssueView(request, target, issue, id, '').tag() + + +@csrf_exempt +def issue_milestone(request, username, projectname, id): + from vilya.models.issue import Issue + from vilya.models.project_issue import ProjectIssue + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + target = CodeDoubanProject.get_by_name(name) + issue_number = id + project_issue = ProjectIssue.get(target.id, + number=issue_number) + issue_id = project_issue.issue_id + issue = Issue.get_cached_issue(issue_id) + return IssueView(request, target, issue, id, '').milestone() + + +@csrf_exempt +def issue_join(request, username, projectname, id): + from vilya.models.issue import Issue + from vilya.models.project_issue import ProjectIssue + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + target = CodeDoubanProject.get_by_name(name) + issue_number = id + project_issue = ProjectIssue.get(target.id, + number=issue_number) + issue_id = project_issue.issue_id + issue = Issue.get_cached_issue(issue_id) + return IssueView(request, target, issue, id, '').join() + + +@csrf_exempt +def issue_leave(request, username, projectname, id): + from vilya.models.issue import Issue + from vilya.models.project_issue import ProjectIssue + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + target = CodeDoubanProject.get_by_name(name) + issue_number = id + project_issue = ProjectIssue.get(target.id, + number=issue_number) + issue_id = project_issue.issue_id + issue = Issue.get_cached_issue(issue_id) + return IssueView(request, target, issue, id, '').leave() + + +@csrf_exempt +def issue_assign(request, username, projectname, id): + from vilya.models.issue import Issue + from vilya.models.project_issue import ProjectIssue + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + target = CodeDoubanProject.get_by_name(name) + issue_number = id + project_issue = ProjectIssue.get(target.id, + number=issue_number) + issue_id = project_issue.issue_id + issue = Issue.get_cached_issue(issue_id) + return IssueView(request, target, issue, id, '').assign() + + +@csrf_exempt +def issues_tags_tag(request, username, projectname, name): + from vilya.models.tag import TagName + from vilya.models.project import CodeDoubanProject + project_name = '/'.join([username, projectname]) + target = CodeDoubanProject.get_by_name(project_name) + + target_type = target.tag_type + target_id = target.id + + tag = TagName.get_by_name_and_target_id(name, target_type, target_id) + if request.method == 'POST': + name = request.POST.get('name') + # TODO: check name + if name and tag.name != name: + tag.update_name(name) + color = request.POST.get('color') + # color: #xxxxxx + # TODO: check color + if color and tag.hex_color != color[1:]: + tag.update_color(color[1:]) + return HttpResponseRedirect(target.url + 'issues') + + +@csrf_exempt +def issue_comments_comment_edit(request, username, projectname, id): + from vilya.models.issue_comment import IssueComment + from vilya.models.project import CodeDoubanProject + project_name = '/'.join([username, projectname]) + project = CodeDoubanProject.get_by_name(project_name) + comment = IssueComment.get(id) + + user = request.user + current_user = request.user + if request.method == 'POST': + if comment.author_id != user.username: + return HttpResponseForbidden() + content = request.POST.get( + 'pull_request_comment', '').decode('utf-8') + comment.update(content) + comment = IssueComment.get(comment.id) + author = user + target = project + return HttpResponse(json.dumps(dict( + r=0, + html=st('/widgets/issue/issue_comment.html', **locals())))) + + +@csrf_exempt +def issue_comments_comment_delete(request, username, projectname, id): + from vilya.models.issue import Issue + from vilya.models.issue_comment import IssueComment + comment = IssueComment.get(id) + + user = request.user + if comment.author_id != user.username: + return HttpResponseForbidden() + issue_id = comment.issue_id + ok = comment.delete() + if not ok: + return HttpResponse(json.dumps({'r': 0})) + pissue = Issue.get_cached_issue(issue_id) + pissue.update_rank_score() + return HttpResponse(json.dumps({'r': 1})) diff --git a/vilya/views/django/project_setting.py b/vilya/views/django/project_setting.py new file mode 100644 index 0000000..9ce518b --- /dev/null +++ b/vilya/views/django/project_setting.py @@ -0,0 +1,238 @@ +# -*- coding: utf-8 -*- + +from django.http import Http404 +from django.http import HttpResponse +from django.http import HttpResponseRedirect +from django.http import HttpResponseForbidden +from django.views.decorators.csrf import csrf_exempt +from vilya.libs.template import st + + +@csrf_exempt +def index(request, username, projectname): + from vilya.models.team import Team + from vilya.models.project import CodeDoubanProject + from vilya.models.user import User + name = '/'.join([username, projectname]) + project = CodeDoubanProject.get_by_name(name) + user = request.user + if not project: + raise Http404 + if not project.is_owner(request.user): + return HttpResponseForbidden() + + if request.method == 'GET': + teams = Team.gets() + owner = User(project.owner_id) + committers = project.get_committers_by_project(project.id) + + if project.fork_from: + fork_from = CodeDoubanProject.get(project.fork_from) + + return HttpResponse(st('settings/main.html', **locals())) + + elif request.method == 'POST': + if user.name == project.owner_id: + summary = request.POST.get('summary', '') + product = request.POST.get('product', '') + intern_banned = request.POST.get('intern_banned', None) + project.update(summary, product, name, intern_banned) + + return HttpResponseRedirect('/%s/settings' % name) + + +def add_committer(request, username, projectname): + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + project = CodeDoubanProject.get_by_name(name) + user = request.user + if request.method == 'POST': + if user.name == project.owner_id: + committers = request.POST.get('username', '') + committers = committers.split(' ') + for committer in committers: + project.add_committer(project.id, committer) + return HttpResponseRedirect('/%s/settings' % name) + + +def del_committer(request, username, projectname): + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + project = CodeDoubanProject.get_by_name(name) + user = request.user + if request.method == 'POST': + if user.name == project.owner_id: + committers = request.POST.get('username', '') + project.del_committer(project.id, committers) + return HttpResponseRedirect('/%s/settings' % name) + + +def sphinx_docs(request, username, projectname): + from tasks import sphinx_builds_add + from vilya.models.sphinx_docs import SphinxDocs + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + project = CodeDoubanProject.get_by_name(name) + user = request.user + docs = SphinxDocs(name) + if request.GET.get('force_rebuild') == 'mq': + sphinx_builds_add(name) + return HttpResponseRedirect('/%s/settings/sphinx_docs' % name) + if request.GET.get('force_rebuild') == 'direct': + docs.build_all() + return HttpResponseRedirect('/%s/settings/sphinx_docs' % name) + tdt = { + 'project': project, + 'request': request, + 'enabled': docs.enabled, + 'last_build': docs.last_build_info(), + 'user': user, + } + return HttpResponse(st('settings/sphinx_docs.html', **tdt)) + + +def hooks_index(request, username, projectname): + # FIXME(xutao) remove TELCHAR_URL + from vilya.models.consts import FEATURE_HOOK_URLS, TELCHAR_URL + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + errors = '' + user = request.user + project = CodeDoubanProject.get_by_name(name) + hooks = [hook for hook in project.hooks + if hook.url not in FEATURE_HOOK_URLS] + enabled_telchar = next((hook for hook in project.hooks + if hook.url == TELCHAR_URL), False) + return HttpResponse(st('settings/hooks.html', **locals())) + + +@csrf_exempt +def hooks_new(request, username, projectname): + from vilya.models.hook import CodeDoubanHook + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + errors = '' + user = request.user + url = request.get_form_var('url') + project = CodeDoubanProject.get_by_name(name) + hooks = project.hooks + if request.method == "POST": + hook = CodeDoubanHook(0, url, project.id) + exists_id = CodeDoubanHook.get_id_by_url(project.id, url) + errors = hook.validate() + if not project.is_owner(user): + errors.append("You can't set hooks for this project") + if exists_id is not None: + errors.append("This hook url has exists") + if not errors: + CodeDoubanHook.add(hook.url, hook.project_id) + return HttpResponseRedirect('/%s/settings/hooks' % name) + return HttpResponse(st('settings/hooks.html', **locals())) + + +def hooks_hook(request, username, projectname, id): + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + user = request.user + hook_id = id + project = CodeDoubanProject.get_by_name(name) + if request.get_form_var('_method') == 'delete' \ + and project.is_owner(user): + hooks = project.hooks + hook = (h for h in hooks if int(h.id) == int(hook_id)).next() + hook.destroy() + return HttpResponseRedirect('/%s/settings/hooks' % name) + + +def conf(request, username, projectname): + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + project = CodeDoubanProject.get_by_name(name) + user = request.user + tdt = { + 'user': user, + 'project': project, + 'request': request, + } + return HttpResponse(st('settings/config.html', **tdt)) + + +def pages(request, username, projectname): + from vilya.models.sphinx_docs import SphinxDocs + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + user = request.user + project = CodeDoubanProject.get_by_name(name) + docs = SphinxDocs(name) + tdt = { + 'project': project, + 'request': request, + 'user': user, + 'docs': docs, + 'last_build': docs.last_build_info(), + } + return HttpResponse(st('settings/pages.html', **tdt)) + + +def transfer_project(request, username, projectname): + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + project = CodeDoubanProject.get_by_name(name) + user_id = request.GET.get('username') + if user_id: + project.transfer_to(user_id) + return HttpResponse('transfer success') + return HttpResponse('please input a username') + + +def rename_project(request, username, projectname): + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + project = CodeDoubanProject.get_by_name(name) + repo_name = request.GET.get('repo_name') + if repo_name: + if project.rename(repo_name) is not False: + return HttpResponseRedirect(project.url) + else: + return HttpResponse("repo name already exist") + return HttpResponse('please input a repo name') + + +def groups_index(request, username, projectname): + from vilya.models.team import Team + from vilya.models.team_group import TeamGroup + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + project = CodeDoubanProject.get_by_name(name) + group_name = request.GET.get('group', '') + if not group_name: + return HttpResponseRedirect("%ssettings/" % project.url) + team, _, group = group_name.rpartition('/') + t = Team.get_by_uid(team) + if not t: + return HttpResponseRedirect("%ssettings/" % project.url) + g = TeamGroup.get(team_id=t.id, name=group) + if not g: + return HttpResponseRedirect("%ssettings/" % project.url) + g.add_project(project_id=project.id) + return HttpResponseRedirect("%ssettings/" % project.url) + + +def groups_destory(request, username, projectname): + from vilya.models.team import Team + from vilya.models.team_group import TeamGroup + from vilya.models.project import CodeDoubanProject + name = '/'.join([username, projectname]) + project = CodeDoubanProject.get_by_name(name) + group_name = request.GET.get('group', '') + if not group_name: + return HttpResponseRedirect("%ssettings/" % project.url) + team, _, group = group_name.rpartition('/') + t = Team.get_by_uid(team) + if not t: + return HttpResponseRedirect("%ssettings/" % project.url) + g = TeamGroup.get(team_id=t.id, name=group) + if not g: + return HttpResponseRedirect("%ssettings/" % project.url) + g.remove_project(project_id=project.id) + return HttpResponseRedirect("%ssettings/" % project.url) diff --git a/vilya/views/django/setting.py b/vilya/views/django/setting.py new file mode 100644 index 0000000..61172b5 --- /dev/null +++ b/vilya/views/django/setting.py @@ -0,0 +1,170 @@ +# -*- coding: utf-8 -*- + +import json +from django.http import HttpResponse +from django.http import HttpResponseRedirect +from django.views.decorators.csrf import csrf_exempt +from vilya.libs.template import st + + +@csrf_exempt +def index(request): + return HttpResponseRedirect("/settings/emails/") + + +@csrf_exempt +def emails(request): + from vilya.models.user import CodeDoubanUserEmails + errors = [] + user = request.user + emails = user.emails + if request.method == "POST": + email = request.POST.get('email') + errors = CodeDoubanUserEmails.validate(user.name, email) + if not errors: + CodeDoubanUserEmails.add(user.name, email) + return HttpResponseRedirect('/settings/emails') + return HttpResponse(st('/settings/emails.html', **locals())) + + +@csrf_exempt +def emails_delete(request, id): + from vilya.models.user import CodeDoubanUserEmails + user = request.user + email = CodeDoubanUserEmails.check_own_by_user(user.name, id) + if email: + email.delete() + return HttpResponseRedirect('/settings/emails') + + +@csrf_exempt +def emails_set_notif(request, id): + from vilya.models.user import CodeDoubanUserEmails + user = request.user + email = CodeDoubanUserEmails.check_own_by_user(user.name, id) + if email: + addr = email.email + user.settings.notif_other_emails \ + = user.settings.notif_other_emails + [addr] + return HttpResponseRedirect('/settings/emails') + + +@csrf_exempt +def emails_un_notif(request, id): + from vilya.models.user import CodeDoubanUserEmails + user = request.user + email = CodeDoubanUserEmails.check_own_by_user(user.name, id) + if email: + addr = email.email + user.settings.notif_other_emails = [ + e for e in user.settings.notif_other_emails + if e != addr] + return HttpResponseRedirect('/settings/emails') + + +@csrf_exempt +def github(request): + from vilya.models.user import CodeDoubanUserGithub + errors = [] + user = request.user + githubs = user.githubs + if request.method == "POST": + user_name = request.POST.get('github') + errors = CodeDoubanUserGithub.validate(user.name, user_name) + if not errors: + CodeDoubanUserGithub.add(user.name, user_name) + return HttpResponseRedirect('/settings/github') + return HttpResponse(st('/settings/github.html', **locals())) + + +@csrf_exempt +def github_delete(request, id): + from vilya.models.user import CodeDoubanUserGithub + if request.POST.get('_method') == 'delete': + user = request.user + github = CodeDoubanUserGithub.check_own_by_user(user.name, id) + if github: + github.delete() + return HttpResponseRedirect('/settings/github') + + +def notification(request): + user = request.user + return HttpResponse(st('settings/notification.html', **locals())) + + +@csrf_exempt +def notification_setting(request): + is_on = request.POST.get('is_on') + notifications_meta = request.POST.get('notifications_meta') + user = request.user + result = "success" + try: + user.settings.__setattr__(notifications_meta, is_on) + except Exception: + result = "fail" + return HttpResponse(json.dumps({"result": result})) + + +@csrf_exempt +def ssh(request): + from vilya.models.sshkey import SSHKey + errors = [] + key_lines = '' + user = request.user + sshkeys = user.sshkeys + if request.method == "POST": + key_lines = request.POST.get('ssh') + newsshkeys = [] + errorkeys = [] + for index, line in enumerate(key_lines.splitlines()): + valid = SSHKey.validate(user.name, line) + if not valid: + errorkeys.append((index, line)) + continue + duplicated = SSHKey.is_duplicated(user.name, line) + if duplicated: + errorkeys.append((index, line)) + continue + newsshkeys.append(line) + + if not errorkeys: + for key in newsshkeys: + SSHKey.add(user.name, key) + return HttpResponseRedirect('/settings/ssh') + + error_prefix = 'Please check your SSH Key, Line: ' + for no, key in errorkeys: + error = error_prefix + '%s ' % no + errors.append(error) + return HttpResponse(st('/settings/ssh.html', **locals())) + + +@csrf_exempt +def ssh_delete(request, id): + from vilya.models.sshkey import SSHKey + user = request.user + if request.POST.get('_method') == 'delete': + sshkey = SSHKey.check_own_by_user(user.name, id) + if sshkey: + sshkey.delete() + return HttpResponseRedirect('/settings/ssh') + + +def codereview(request): + user = request.user + return HttpResponse(st('settings/codereview.html', **locals())) + + +@csrf_exempt +def codereview_setting(request): + is_enable = request.POST.get('is_enable') + field = request.POST.get('field') + user = request.user + result = "success" + origin = user.settings.__getattr__(field) + try: + user.settings.__setattr__(field, is_enable) + except Exception: + result = "fail" + return HttpResponse(json.dumps({"result": result, "origin": origin})) diff --git a/vilya/views/django/team.py b/vilya/views/django/team.py new file mode 100644 index 0000000..91e04d1 --- /dev/null +++ b/vilya/views/django/team.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from django.http import HttpResponse +from vilya.libs.template import st + + +def teams(request): + from vilya.models.team import Team + TEAMS_COUNT_PER_PAGE = 10 + page = request.GET.get('page', 1) + start = TEAMS_COUNT_PER_PAGE * (int(page) - 1) + teams = Team.gets_by_page(start=start, limit=TEAMS_COUNT_PER_PAGE) + total_teams = len(Team.gets()) + n_pages = (total_teams - 1) / TEAMS_COUNT_PER_PAGE + 1 if total_teams else 1 # noqa + + return HttpResponse(st('/teams/teams.html', **locals())) diff --git a/vilya/views/django/team_issue.py b/vilya/views/django/team_issue.py new file mode 100644 index 0000000..11ca6f9 --- /dev/null +++ b/vilya/views/django/team_issue.py @@ -0,0 +1,153 @@ +# -*- coding: utf-8 -*- + +import json +from django.http import HttpResponse +from django.http import HttpResponseForbidden +from django.views.decorators.csrf import csrf_exempt +from vilya.libs.template import st + +from vilya.views.django.issue import IssueView + + +def issue(request, name, id): + from vilya.models.team import Team + from vilya.models.team_issue import TeamIssue + from vilya.models.issue import Issue + target = Team.get_by_uid(name) + issue_number = id + team_issue = TeamIssue.get(target.id, number=issue_number) + issue_id = team_issue.issue_id + issue = Issue.get_cached_issue(issue_id) + issue_template = 'issue/team_issue.html' + return IssueView(request, target, issue, id, issue_template).index() + + +@csrf_exempt +def issue_upvote(request, name, id): + from vilya.models.team import Team + from vilya.models.team_issue import TeamIssue + from vilya.models.issue import Issue + target = Team.get_by_uid(name) + issue_number = id + team_issue = TeamIssue.get(target.id, number=issue_number) + issue_id = team_issue.issue_id + issue = Issue.get_cached_issue(issue_id) + return IssueView(request, target, issue, id, '').upvote() + + +@csrf_exempt +def issue_comment(request, name, id): + from vilya.models.team import Team + from vilya.models.team_issue import TeamIssue + from vilya.models.issue import Issue + target = Team.get_by_uid(name) + issue_number = id + team_issue = TeamIssue.get(target.id, number=issue_number) + issue_id = team_issue.issue_id + issue = Issue.get_cached_issue(issue_id) + return IssueView(request, target, issue, id, '').comment() + + +@csrf_exempt +def issue_tag(request, name, id): + from vilya.models.team import Team + from vilya.models.team_issue import TeamIssue + from vilya.models.issue import Issue + target = Team.get_by_uid(name) + issue_number = id + team_issue = TeamIssue.get(target.id, number=issue_number) + issue_id = team_issue.issue_id + issue = Issue.get_cached_issue(issue_id) + return IssueView(request, target, issue, id, '').tag() + + +@csrf_exempt +def issue_milestone(request, name, id): + from vilya.models.team import Team + from vilya.models.team_issue import TeamIssue + from vilya.models.issue import Issue + target = Team.get_by_uid(name) + issue_number = id + team_issue = TeamIssue.get(target.id, number=issue_number) + issue_id = team_issue.issue_id + issue = Issue.get_cached_issue(issue_id) + return IssueView(request, target, issue, id, '').milestone() + + +@csrf_exempt +def issue_join(request, name, id): + from vilya.models.team import Team + from vilya.models.team_issue import TeamIssue + from vilya.models.issue import Issue + target = Team.get_by_uid(name) + issue_number = id + team_issue = TeamIssue.get(target.id, number=issue_number) + issue_id = team_issue.issue_id + issue = Issue.get_cached_issue(issue_id) + return IssueView(request, target, issue, id, '').join() + + +@csrf_exempt +def issue_leave(request, name, id): + from vilya.models.team import Team + from vilya.models.team_issue import TeamIssue + from vilya.models.issue import Issue + target = Team.get_by_uid(name) + issue_number = id + team_issue = TeamIssue.get(target.id, number=issue_number) + issue_id = team_issue.issue_id + issue = Issue.get_cached_issue(issue_id) + return IssueView(request, target, issue, id, '').leave() + + +@csrf_exempt +def issue_assign(request, name, id): + from vilya.models.team import Team + from vilya.models.team_issue import TeamIssue + from vilya.models.issue import Issue + target = Team.get_by_uid(name) + issue_number = id + team_issue = TeamIssue.get(target.id, number=issue_number) + issue_id = team_issue.issue_id + issue = Issue.get_cached_issue(issue_id) + return IssueView(request, target, issue, id, '').assign() + + +@csrf_exempt +def comment_edit(request, name, id): + from vilya.models.team import Team + from vilya.models.issue_comment import IssueComment + target = Team.get_by_uid(name) + comment = IssueComment.get(id) + + user = request.user + current_user = request.user + if request.method == 'POST': + if comment.author_id != user.username: + return HttpResponseForbidden() + content = request.POST.get( + 'pull_request_comment', '').decode('utf-8') + comment.update(content) + comment = IssueComment.get(comment.id) + author = user + return HttpResponse(json.dumps(dict( + r=0, + html=st('/widgets/issue/issue_comment.html', **locals())))) + + +@csrf_exempt +def comment_delete(request, name, id): + from vilya.models.issue import Issue + from vilya.models.issue_comment import IssueComment + comment = IssueComment.get(id) + + user = request.user + if comment.author_id != user.username: + return HttpResponseForbidden() + issue_id = comment.issue_id + ok = comment.delete() + if not ok: + return HttpResponse(json.dumps({'r': 0})) + pissue = Issue.get_cached_issue(issue_id) + pissue.update_rank_score() + return HttpResponse(json.dumps({'r': 1})) diff --git a/vilya/views/trello.py b/vilya/views/django/trello.py similarity index 63% rename from vilya/views/trello.py rename to vilya/views/django/trello.py index 4639cf1..c653a2f 100644 --- a/vilya/views/trello.py +++ b/vilya/views/django/trello.py @@ -1,23 +1,16 @@ -# coding=utf8 +# -*- coding: utf-8 -*- -__author__ = 'torpedoallen' - -from quixote.errors import AccessError, TraversalError -from vilya.models.trello.wrapper import DoubanTrelloClient +from django.http import HttpResponseRedirect +from django.http import HttpResponseBadRequest from vilya.config import DOMAIN -_q_exports = ['bind', 'unbind'] - - -def _q_access(request): - if not request.user: - raise AccessError('must login') - def bind(request): + from vilya.models.trello.wrapper import DoubanTrelloClient user = request.user if user.trello_access_token: - raise TraversalError('already binded') + return HttpResponseBadRequest('already binded') + dtclient = DoubanTrelloClient() if not user.trello_request_token: # get request token @@ -28,15 +21,15 @@ def bind(request): url = '%s%s' % (DOMAIN, request.environ.get('REQUEST_URI')) authorize_url = dtclient.get_authorisation_url( request_token['oauth_token'], url) - return request.redirect(authorize_url) + return HttpResponseRedirect(authorize_url) else: # get access token - oauth_verifier = request.get_form_var("oauth_verifier") + oauth_verifier = request.GET.get("oauth_verifier") access_token = dtclient.get_access_token( user.trello_request_token, oauth_verifier) user.trello_access_token = access_token url = '%s%s' % (DOMAIN, user.url) - return request.redirect(url) + return HttpResponseRedirect(url) def unbind(request): @@ -44,4 +37,4 @@ def unbind(request): user.trello_request_token = None user.trello_access_token = None url = '%s%s' % (DOMAIN, user.url) - return request.redirect(url) + return HttpResponseRedirect(url) diff --git a/vilya/views/django/user.py b/vilya/views/django/user.py new file mode 100644 index 0000000..8490965 --- /dev/null +++ b/vilya/views/django/user.py @@ -0,0 +1,247 @@ +# -*- coding: utf-8 -*- + +import json +from django.http import HttpResponse +from django.http import JsonResponse +from django.http import HttpResponseRedirect +from django.views.decorators.csrf import csrf_exempt +from django.contrib.auth.models import User +from vilya.libs.template import st + +FOLLOW_LIST_USER_COUNT = 24 + + +def index(request, username): + from vilya.models.feed import get_user_feed + from vilya.models.project import CodeDoubanProject + name = username + user = request.user + your_projects = CodeDoubanProject.get_projects(owner=name, + sortby='lru') + actions = get_user_feed(name).get_actions(0, 20) + followers_count = user.followers_count + following_count = user.following_count + return HttpResponse(st('people.html', **locals())) + + +@csrf_exempt +def login(request): + from django.contrib.auth import authenticate, login + from vilya.models.user import User + + if request.method == 'POST': + name = request.POST.get('username') + password = request.POST.get('password') + + user = authenticate(username=name, password=password) + if user is not None: + continue_url = request.GET.get('continue', '') \ + or request.META.get('Referer', '') + + # django user + login(request, user) + + # quixote user + request.user = User(user.username) + # FIXME(xutao) jsx + #return JsonResponse({"r": 0, "continue": continue_url or "/"}) + return HttpResponse(json.dumps({'r': 0, "continue": continue_url or "/"})) + else: + message = '用户名或密码错误!' + # FIXME(xutao) jsx + #return JsonResponse({"r": 1, 'message': message}) + return HttpResponse(json.dumps({"r": 1, 'message': message})) + return HttpResponse(st('login.html')) + + +@csrf_exempt +def register(request): + from django.contrib.auth import login + from vilya.models.nuser import User2 + if request.method == 'POST': + email = request.POST.get('email') + password = request.POST.get('password') + if not email: + # FIXME(xutao) jsx + #return JsonResponse({'r': 1, 'message': 'Email没有指定'}) + return HttpResponse(json.dumps({'r': 1, 'message': 'Email没有指定'})) + + user_name = email.split('@')[0] + if User2.is_exists(user_name): + # FIXME(xutao) jsx + #return JsonResponse({'r': 1, 'message': '用户已存在'}) + return HttpResponse(json.dumps({'r': 1, 'message': '用户已存在'})) + + # django user + # FIXME(xutao) get user name from user input + user = User.objects.create_user(user_name, email, password) + user.save() + login(request, user) + + # quixote user + code_user = User2.add(user_name, password) + + # FIXME(xutao) jsx + #return JsonResponse({'r': 0}) + return HttpResponse(json.dumps({'r': 0})) + return HttpResponse(st('register.html')) + + +@csrf_exempt +def logout(request): + from django.contrib.auth import logout + from vilya.models.user import User + + if request.method == 'POST': + continue_url = request.GET.get('continue', '') or request.META.get('Referer', '') + logout(request) + return HttpResponseRedirect(continue_url or '/') + + if request.user: + user = User(request.user.username) + context = {} + context['current_user'] = user + return HttpResponse(st('logout.html', **context)) + + +def badges(request, username): + import itertools + from datetime import datetime, timedelta + from vilya.models.badge import Badge + name = username + badge_items = Badge.get_badge_items() + _date_badge_items = [i for i in badge_items if i.date] + items_groupby_date = itertools.groupby( + _date_badge_items, lambda badge_item: badge_item.date.date()) + today = datetime.now().date() + yesterday = today - timedelta(days=1) + return HttpResponse(st('badge/timeline.html', **locals())) + + +def follow(request, username): + # FIXME(xutao) + action = 1 + msg = "success" + return JsonResponse({"action": action, "msg": msg}) + + +def unfollow(request, username): + # FIXME(xutao) + action = -1 + msg = "success" + return JsonResponse({"action": action, "msg": msg}) + + +def following(request, username): + from vilya.models.user import User + name = username + list_type = "following" + user_list = User(name).get_following() + return HttpResponse(follow_list(request, list_type, user_list)) + + +def followers(request, username): + from vilya.models.user import User + name = username + list_type = "followers" + user_list = User(name).get_followers() + return HttpResponse(follow_list(request, list_type, user_list)) + + +def add_rec(request): + # TODO(xutao) + return HttpResponse(st('people_add_rec.html', **locals())) + + +def praises(request, username): + from vilya.models.recommendation import Recommendation + name = username + recs = Recommendation.gets_by_user(name) + return HttpResponse(st('people_recs.html', **locals())) + + +def contributions(request, username): + import time + from datetime import datetime + from vilya.models.contributions import UserContributions + name = username + res = {} + for date_, contribution in UserContributions.get_by_user( + name).iteritems(): + timestamp = time.mktime( + datetime.strptime(date_, '%Y-%m-%d').timetuple()) + score = contribution[0] + res.setdefault(timestamp, score) + return JsonResponse(res) + + +def contribution_detail(request, username): + from vilya.models.contributions import UserContributions + from vilya.models.ticket import Ticket + import dateutil.parser + name = username + req_date = request.GET.get('date') + if req_date: + try: + req_date = dateutil.parser.parse( + req_date).astimezone(dateutil.tz.tzoffset('EST', 8*3600)) + except ValueError as e: + return "" + contributions = UserContributions.get_by_date(name, req_date) + owned = contributions.get('owned_tickets') + commented = contributions.get('commented_tickets') + owned_tickets = filter(None, [Ticket.get(id_) for id_ in owned]) + commented_tickets = filter(None, [Ticket.get(comment[0]) + for comment in commented]) + return HttpResponse(st('people_contribution_detail.html', **locals())) + return HttpResponse("") + + +def follow_list(name, request, list_type, user_list): + page = int(request.get_form_var('page', 1)) + count = len(user_list) + start = FOLLOW_LIST_USER_COUNT * (page - 1) + n_pages = count / FOLLOW_LIST_USER_COUNT + 1 + user_list = user_list[start:(start + FOLLOW_LIST_USER_COUNT)] + current_user = user = request.user + current_user_following = current_user.get_following() \ + if current_user else [] + return st('follow-list.html', **locals()) + + +def watching(request): + user = request.user + if user: + watched_projects = user.watched_projects + return HttpResponse(st('/watching.html', **locals())) + return HttpResponseRedirect("/hub/public_timeline") + + +def favorites(request): + from vilya.models.user_fav import UserFavItem + if not request.user: + return HttpResponseRedirect('/') + favs = UserFavItem.gets_by_user_kind(request.user.username) + return HttpResponse(st('/favorites.html', **locals())) + + +def praise_index(request): + from vilya.models.recommendation import Recommendation + """recommendations timeline""" + start = request.GET.get('start') + start = start and start.isdigit() and int(start) or 0 + limit = 20 + recs = Recommendation.gets(start=start, limit=limit) + return HttpResponse(st('recommendations.html', **locals())) + + +def praise_vote(request): + from vilya.models.recommendation import Recommendation + user = request.user + if user: + rec_id = request.GET.get('rec_id') + r = Recommendation.get(rec_id) + if r: + r.add_vote(user.name) + return HttpResponse(json.dumps({'r': 1})) + return HttpResponse(json.dumps({'r': 0})) diff --git a/vilya/views/django/views.py b/vilya/views/django/views.py new file mode 100644 index 0000000..a829399 --- /dev/null +++ b/vilya/views/django/views.py @@ -0,0 +1,285 @@ +# -*- coding: utf-8 -*- + +import json +from django.http import Http404 +from django.http import HttpResponse +from django.views.decorators.csrf import csrf_exempt +from vilya.libs.template import st + + +def mirrors(request): + from vilya.models.project import CodeDoubanProject + your_projects = CodeDoubanProject.get_projects( + owner="mirror", sortby='sumup') + return HttpResponse(st('/mirrors.html', **locals())) + + +def m_index(request): + return HttpResponse(st('/m/feed.html', **locals())) + + +def m_public_timeline(request): + is_public = True + return HttpResponse(st('/m/feed.html', **locals())) + + +def m_actions(request): + from vilya.models.feed import get_user_inbox, get_public_feed + MAX_ACT_COUNT = 100 + + since_id = request.GET.get('since_id', '') + is_public = request.GET.get('is_public', '') + user = request.user + all_actions = [] + if is_public == 'true': + all_actions = get_public_feed().get_actions(0, MAX_ACT_COUNT) + elif user: + all_actions = get_user_inbox(user.username).get_actions( + 0, MAX_ACT_COUNT) + if since_id: + actions = [] + for action in all_actions: + if action.get('uid') == since_id: + break + actions.append(action) + else: + actions = all_actions + return HttpResponse(st('/m/actions.html', **locals())) + + +@csrf_exempt +def j_pull_edit(request, id): + from vilya.models.ticket import Ticket + from vilya.models.project import CodeDoubanProject + from vilya.models.pull import PullRequest + from vilya.libs.text import parse_emoji, render_markdown_with_project + ticket = Ticket.get(id) + if not ticket: + raise Http404() + + ticket_id = ticket.ticket_number + project = CodeDoubanProject.get(ticket.project_id) + pullreq = PullRequest.get_by_proj_and_ticket(project.id, ticket_id) + proj_name = project.name + if request.method == 'POST': + title = request.POST.get('title', '').decode('utf-8') + content = request.POST.get('content', '').decode('utf-8') + user = request.user + user = user.name if user else None + if user == ticket.author: + ticket.update(title, content) + content_html = render_markdown_with_project( + content, proj_name) + content_html += st('/widgets/markdown_checklist.html', + **locals()) + return HttpResponse(json.dumps({'r': 0, + 'title': title, + 'content': content, + 'title_html': parse_emoji(title), + 'content_html': content_html})) + return HttpResponse(json.dumps({'r': 1})) + + +@csrf_exempt +def j_issue_edit(request, id): + from vilya.libs.text import ( + parse_emoji, + render_markdown_with_project, + render_markdown + ) + from vilya.models.issue import Issue + issue = Issue.get_cached_issue(id) + if not issue: + raise Http404() + + if request.method == 'POST': + title = request.POST.get('title', '').decode('utf-8') + if not title.strip(): + return HttpResponse(json.dumps({'r': 1})) + content = request.POST.get('content', '').decode('utf-8') + user = request.user + user = user.name if user else None + if issue and user == issue.creator_id: + issue.update(title, content) + if issue == "project": + content_html = render_markdown_with_project( + content, issue.target.name) + else: + content_html = render_markdown(content) + content_html += st('/widgets/markdown_checklist.html', + **locals()) + return HttpResponse(json.dumps({'r': 0, + 'title': title, + 'content': content, + 'title_html': parse_emoji(title), + 'content_html': content_html})) + return HttpResponse(json.dumps({'r': 1})) + + +@csrf_exempt +def j_issue_delete_tag(request): + from vilya.views.models.tag import ( + TagName, + Tag, + TAG_TYPE_PROJECT_ISSUE, + TAG_TYPE_TEAM_ISSUE + ) + from vilya.views.models.project import CodeDoubanProject + from vilya.views.models.team import Team + # FIXME: ugly + if request.method == "POST": + user = request.user + if not user: + return HttpResponse(json.dumps({'r': 0, 'msg': '未登录,请先登录'})) + tag_name = request.POST.get('tag_name', '').decode('utf-8') + tag_type = request.POST.get('tag_type', '') + tag_target_id = request.POST.get('tag_target_id', '') + + if not tag_name: + return HttpResponse(json.dumps({'r': 0, 'msg': 'tag不能为空'})) + try: + tag_type, tag_target_id = int(tag_type), int(tag_target_id) + except: + return HttpResponse(json.dumps({'r': 0, 'msg': '错误的数据类型'})) + + if tag_type == TAG_TYPE_PROJECT_ISSUE: + target = CodeDoubanProject.get(tag_target_id) + elif tag_type == TAG_TYPE_TEAM_ISSUE: + target = Team.get(tag_target_id) + else: + return HttpResponse(json.dumps({'r': 0, 'msg': '错误的数据类型'})) + + if not target.is_admin(user.name): + return HttpResponse(json.dumps({'r': 0, 'msg': '没有操作权限'})) + + tname = TagName.get_by_name_and_target_id( + tag_name, tag_type, target.id) + if not tname: + return HttpResponse(json.dumps({'r': 0, 'msg': 'tag不存在'})) + + tags = Tag.gets_by_tag_id(tname.id) + for tag in tags: + tag.delete() + tname.delete() + return HttpResponse(json.dumps({'r': 1, 'msg': '删除成功'})) + + +@csrf_exempt +def j_chat_delete_room(request): + from vilya.models.room import Room + from vilya.models.message import get_room_message + room_name = request.POST.get('room_name', '') + if Room.delete(room_name): + get_room_message(room_name).delete_by_key() + return HttpResponse(json.dumps({'r': 1, 'msg': '删除成功'})) + return HttpResponse(json.dumps({'r': 0, 'msg': '删除失败, 可能该room已被删除'})) + + +@csrf_exempt +def j_chat_room(request, name): + from datetime import datetime + from vilya.models.message import get_room_message + from vilya.views.util import render_message + from vilya.models.room import Room + room_name = name + if request.method == "POST": + content = request.POST.get('message') + author = request.user.username + date = datetime.now() + message_data = { + "content": content, + "author": author, + "date": date + } + room_message = get_room_message(room_name) + room_message.add_message(message_data) + return HttpResponse(json.dumps({'r': 1})) + if request.method == "GET": + if room_name != 'lobby' and not Room.exists(room_name): + return HttpResponse(json.dumps({'r': 0, 'msg': 'room not exists'})) + room_message = get_room_message(room_name) + messages = room_message.get_messages() + render_messages = [render_message(m) for m in messages] + return HttpResponse(json.dumps({'r': 1, 'msg': render_messages})) + + +@csrf_exempt +def j_fav(request): + from vilya.models.user_fav import UserFavItem + from vilya.models.consts import K_PULL, K_ISSUE + user = request.user + tid = request.POST.get('tid', '') + tkind = request.POST.get('tkind', '0') + tkind = int(tkind) + if not user or tkind not in [K_PULL, K_ISSUE]: + return HttpResponse(json.dumps({'r': 1})) + if request.method == "POST": + UserFavItem.add(user.username, tid, tkind) + elif request.method == 'DELETE': + UserFavItem.delete_by_user_target_kind(user.username, tid, tkind) + else: + return HttpResponse(json.dumps({'r': 1})) + return HttpResponse(json.dumps({'r': 0})) + + +@csrf_exempt +def j_hooks_telchar(request, id): + from vilya.models.project import CodeDoubanProject + from vilya.models.hook import CodeDoubanHook + from vilya.models.consts import TELCHAR_URL + user = request.user + project = CodeDoubanProject.get(id) + url = TELCHAR_URL + + if project.is_owner(user): + is_disable = request.POST.get('close', '') + if is_disable: + hook = CodeDoubanHook.get_by_url(url) + if hook: + hook.destroy() + status = 0 + else: + CodeDoubanHook.add(url, id) + status = 1 + return HttpResponse(json.dumps({'r': 0, 'status': status})) + return HttpResponse(json.dumps({'r': 1})) + + +def j_more_pub(request, id): + from vilya.models.feed import get_public_feed, PAGE_ACTIONS_COUNT + from vilya.views.util import render_actions + num = int(id) + actions = get_public_feed().get_actions(start=num, stop=num+PAGE_ACTIONS_COUNT-1) + length = len(actions) + render_html = render_actions(actions, show_avatar=True) + return HttpResponse(json.dumps({'result': render_html, 'length': length})) + + +def j_more_userfeed(request, number): + from vilya.models.feed import get_user_inbox, PAGE_ACTIONS_COUNT + from vilya.views.util import render_actions + user = request.user + if not user or not number.isdigit(): + raise Http404() + num = int(number) + actions = get_user_inbox(user.username).get_actions(start=num, stop=num+PAGE_ACTIONS_COUNT-1) + length = len(actions) + render_html = render_actions(actions, show_avatar=True) + return HttpResponse(json.dumps({'result': render_html, 'length': length})) + + +def j_more_notify(request, id): + return HttpResponse(json.dumps({'r': 1})) + + +def j_more_team(request, name, number): + from vilya.models.feed import get_team_feed, PAGE_ACTIONS_COUNT + from vilya.models.team import Team + from vilya.views.util import render_actions + num = int(number) + team = Team.get_by_uid(name) + actions = get_team_feed(team.id).get_actions(start=num, + stop=num+PAGE_ACTIONS_COUNT-1) + length = len(actions) + render_html = render_actions(actions, show_avatar=True) + return HttpResponse(json.dumps({'result': render_html, 'length': length})) diff --git a/vilya/views/favorites.py b/vilya/views/favorites.py deleted file mode 100644 index f9f9712..0000000 --- a/vilya/views/favorites.py +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- - -from vilya.libs.template import st -from vilya.models.user_fav import UserFavItem - -_q_exports = [] - - -def _q_index(request): - if not request.user: - return request.redirect('/') - favs = UserFavItem.gets_by_user_kind(request.user.username) - return st('/favorites.html', **locals()) diff --git a/vilya/views/fetch.py b/vilya/views/fetch.py deleted file mode 100644 index f53b688..0000000 --- a/vilya/views/fetch.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- - -import json - -from tasks import fetch_mirror_project -from vilya.views.util import require_login_json, error_message - -_q_exports = [] - - -def _q_lookup(request, proj_id): - if request.method == "POST": - return fetch(request, proj_id) - return error_message("bad request") - - -@require_login_json -def fetch(request, proj_id): - fetch_mirror_project(proj_id) - return json.dumps({"ok": 1}) diff --git a/vilya/views/gist.py b/vilya/views/gist.py deleted file mode 100644 index a493bc1..0000000 --- a/vilya/views/gist.py +++ /dev/null @@ -1,449 +0,0 @@ -# -*- coding: utf-8 -*- - -import json -import inspect -from quixote.errors import TraversalError -from quixote.errors import AccessError - -from vilya.libs.template import st, request -from vilya.models.user import User -from vilya.models.gist import Gist -from vilya.models.gist_star import GistStar -from vilya.models.gist_comment import GistComment -from vilya.libs.text import highlight_code -from vilya.views.util import is_mobile_device -from vilya.config import DOMAIN -from tasks import index_a_gist - -_q_exports = ['discover', 'forked', 'starred'] - - -def _q_index(request): - user = request.user - if request.method == 'POST': - desc, is_public, names, contents, oids = _get_req_gist_data(request) - user = request.user - owner_id = user and user.username or Gist.ANONYMOUS - gist = Gist.add(desc, owner_id, is_public, names, contents) - return request.redirect(gist.url) - - tdt = dict(request=request, gists=[], user=user) - if user: - gists = Gist.gets_by_owner(user.username, limit=4) - tdt.update(dict(gists=gists)) - - if is_mobile_device(request): - return st('/m/gist/index.html', **tdt) - return st('/gist/index.html', **tdt) - - -def _discover(request): - user = request.user - name = inspect.stack()[1][3] - (page, start, link_prev, link_next, sort, - direction) = make_page_args(request, name) - gists = Gist.discover(name, sort, direction, start) - tdt = dict( - request=request, - gists=gists, - page=page, - link_prev=link_prev, - link_next=link_next, - sort=sort, - direction=direction, - user=user - ) - return st('/gist/gists.html', **tdt) - - -def discover(request): - return _discover(request) - - -def forked(request): - return _discover(request) - - -def starred(request): - return _discover(request) - - -def _q_lookup(request, item): - if item.isdigit(): - return GistUI(item) - - if item.count('.') == 1: - gid, extend = item.split('.') - if extend == 'js' and gid.isdigit(): - return GistEmbedUI(gid) - - return UserGistUI(item) - - -class GistUI: - _q_exports = ['revisions', 'forks', 'stars', 'fork', 'star', 'unstar', - 'edit', 'delete', 'comments', 'download', 're_index'] - - @property - def comments(self): - return GistCommentUI(self.id) - - def __init__(self, id): - self.id = id - self.gist = Gist.get(id) - - def _q_index(self, request): - user = request.user - tdt = dict(request=request, gist=self.gist, ref='master', user=user) - if is_mobile_device(request): - return st('/m/gist/gist_detail.html', **tdt) - return st('/gist/gist_detail.html', **tdt) - - def _q_lookup(self, request, sha1): - user = request.user - if sha1 == 'raw': - return RawGistUI(self.id) - if sha1 is None or not self.gist.repo.is_commit(sha1): - return TraversalError() - tdt = {'request': request, - 'gist': self.gist, - 'ref': sha1, - 'user': user} - return st('/gist/gist_detail.html', **tdt) - - def edit(self, request): - gist = self.gist - user = request.user - if not user or user.username != gist.owner_id: - raise AccessError() - if request.method == 'POST': - desc, is_public, names, contents, oids = _get_req_gist_data( - request) - gist.update(desc, names, contents, oids) - return request.redirect(gist.url) - tdt = dict(request=request, gist=gist, user=user) - if is_mobile_device(request): - return st('/m/gist/edit.html', **tdt) - return st('/gist/edit.html', **tdt) - - def delete(self, request): - gist = self.gist - user = request.user - if not user or user.username != gist.owner_id: - raise AccessError() - gist.delete() - return request.redirect('/gist/%s' % user.username) - - def revisions(self, request): - user = request.user - gist = self.gist - page = int(request.get_form_var('page', 1)) - skip = 3 * (page - 1) - revlist = gist.get_revlist_with_renames(max_count=3, skip=skip) - link_prev = _make_links(self.id, int(page) - 1, ext="revisions") - if revlist: - link_next = _make_links(self.id, int(page) + 1, ext="revisions") - else: - link_next = '' - content = [] - for r in revlist: - # FIXME: try-except ? - content.append(gist.repo.get_diff(r.sha, rename_detection=True)) - tdt = { - 'request': request, - 'gist': gist, - 'content': content, - 'revlist': revlist, - 'link_prev': link_prev, - 'link_next': link_next, - 'user': user, - 'current_user': user, - } - return st('/gist/gist_revisions.html', **tdt) - - def forks(self, request): - user = request.user - gist = self.gist - tdt = dict(request=request, gist=gist, user=user) - return st('/gist/gist_forks.html', **tdt) - - def stars(self, request): - user = request.user - gist = self.gist - tdt = dict(request=request, gist=gist, user=user) - return st('/gist/gist_stars.html', **tdt) - - def fork(self, request): - gist = self.gist - new_gist = gist.fork(request.user.username) - return request.redirect(new_gist.url) - - def star(self, request): - GistStar.add(self.id, request.user.username) - return request.redirect(self.gist.url) - - def unstar(self, request): - star = GistStar.get_by_gist_and_user(self.id, request.user.username) - if star: - star.delete() - return request.redirect(self.gist.url) - - def download(self, request): - request.response.set_content_type("application/x-gzip") - request.response.set_header("Content-Disposition", - "filename=code_gist_%s.tar.gz" % self.id) - return self.gist.repo.archive(name="code_gist_%s" % self.id) - - def re_index(self, request): - index_a_gist(self.id) - return request.redirect(self.gist.url) - - def _q_access(self, request): - gist = self.gist - user = request.user - - if not gist: - raise TraversalError() - - if not gist.is_public: - if not user or user.username != gist.owner_id: - raise AccessError() - - -class RawGistUI(object): - - _q_exports = [] - - def __init__(self, id): - self.rev = '' - self.gist = Gist.get(id) - - def _q_lookup(self, request, rev): - self.rev = rev - return RecursorGistUI(self.gist, self.rev) - - -class RecursorGistUI(object): - - _q_exports = [] - - def __init__(self, gist, rev): - self.gist = gist - self.rev = rev - self.path = '' - - def _q_lookup(self, request, path): - self.path = path - return self - - def __call__(self, request): - try: - # TODO: clean this - text = self.gist.get_file(self.path, rev=self.rev) - except IOError: - raise TraversalError() - if isinstance(text, bool) and text is False: - raise TraversalError() - resp = request.response - resp.set_header("Content-Type", "text/plain; charset=utf-8") - return text.encode('utf-8') - - -class UserGistUI: - _q_exports = ['forked', 'starred', 'public', 'secret'] - - def __init__(self, name): - self.name = name - self.user = User(name) - - current_user = request.user - self.is_self = current_user and current_user.username == self.name - ext = request.get_path().split('/')[-1] - (self.page, self.start, self.link_prev, self.link_next, self.sort, - self.direction) =\ - make_page_args(request, self.name, ext=ext) - self.n_all = Gist.count_user_all(self.name, self.is_self) - self.n_fork = Gist.count_user_fork(self.name) - self.n_star = Gist.count_user_star(self.name) - - if self.sort not in ('created', 'updated') \ - or self.direction not in ('desc', 'asc'): - raise TraversalError() - - def _render(self, request, gists): - user = self.user - tdt = { - 'request': request, - 'gists': gists, - 'user': user, - 'page': int(self.page), - 'link_prev': self.link_prev, - 'link_next': self.link_next, - 'n_all': self.n_all, - 'n_fork': self.n_fork, - 'n_star': self.n_star, - 'sort': self.sort, - 'direction': self.direction - } - if is_mobile_device(request): - return st('/m/gist/user_gists.html', **tdt) - return st('/gist/user_gists.html', **tdt) - - def _q_index(self, request): - gists = Gist.gets_by_owner( - self.name, is_self=self.is_self, start=self.start, - limit=5, sort=self.sort, direction=self.direction) - return self._render(request, gists) - - def forked(self, request): - gists = Gist.forks_by_user(self.name, start=self.start, - limit=5, sort=self.sort, - direction=self.direction) - return self._render(request, gists) - - def starred(self, request): - gists = Gist.stars_by_user(self.name, start=self.start, limit=5) - return self._render(request, gists) - - def public(self, request): - gists = Gist.publics_by_user(self.name, start=self.start, limit=5, - sort=self.sort, direction=self.direction) - return self._render(request, gists) - - def secret(self, request): - current_user = request.user - if not current_user or current_user.username != self.name: - return request.redirect('/gist/%s' % self.name) - - gists = Gist.secrets_by_user(self.name, start=self.start, limit=5, - sort=self.sort, direction=self.direction) - return self._render(request, gists) - - def _q_lookup(self, request, item): - gid = item - extend = None - if item.count('.') == 1: - gid, extend = item.split('.') - - if not gid.isdigit(): - raise TraversalError() - - gist = Gist.get(gid) - - if not gist or gist.owner_id != self.name: - raise TraversalError() - - if extend == 'js': - return GistEmbedUI(gid) - - return GistUI(gid) - - -EMBED_CSS = """ - - -""" % (DOMAIN, DOMAIN) - -EMBED_HEAD = "
    " -EMBED_FOOTER = "
    " - -SRC_FORMAT = """ -
    -
    -
    %s
    -
    - view raw - %s # noqa - This Gist brought to you by Code. -
    -
    -
    -""" - - -class GistEmbedUI(object): - _q_exports = [] - - def __init__(self, gist_id): - self.gist_id = gist_id - - def __call__(self, request): - resp = request.response - resp.set_header("Content-Type", "text/javascript") - resp.set_header('Expires', 'Sun, 1 Jan 2006 01:00:00 GMT') - resp.set_header('Pragma', 'no-cache') - resp.set_header('Cache-Control', 'must-revalidate, no-cache, private') - if not self.gist_id.isdigit() or not Gist.get(self.gist_id): - return "document.write('NOT EXIST GIST')" # noqa - gist = Gist.get(self.gist_id) - html = EMBED_CSS + EMBED_HEAD % gist.id - for path in gist.files: - path = path.encode('utf8') - # TODO: clean this - src = gist.get_file(path, rev='HEAD') - src = highlight_code(path, src) - src = src.replace('"', '\"').replace("'", "\'") - html += SRC_FORMAT % (src, DOMAIN, gist.id, path, DOMAIN, - gist.id, path, path, gist.url, DOMAIN) - - html += EMBED_FOOTER - html = html.replace('\n', '\\n') - return "document.write('%s')" % html - - -class GistCommentUI(object): - - _q_exports = [] - - def __init__(self, gist_id): - self.gist = Gist.get(gist_id) - - def _q_index(self, request): - if request.method == 'POST': - content = request.get_form_var('content', '') - if content: - GistComment.add(self.gist.id, request.user.username, content) - return request.redirect(self.gist.url) - - def _q_lookup(self, request, comment_id): - if request.method == 'POST': - act = request.get_form_var('act', None) - if act and act in ('delete', 'update'): - comment = GistComment.get(comment_id) - if act == 'delete' and comment: - if comment.can_delete(request.user.username): - comment.delete() - return json.dumps({'r': 1}) - raise TraversalError( - "Unable to delete comment %s" % comment_id) - return request.redirect(self.gist.url) - - -def _get_req_gist_data(request): - _form = request.form - desc = _form.get('desc', '') - is_public = _form.get('gist_public', '1') - gist_names = _form.get('gist_name', '') - gist_contents = _form.get('gist_content', '') - gist_oids = _form.get('oid', '') - return (desc, is_public, gist_names, gist_contents, gist_oids) - - -def _make_links(name, page, ext=''): - if page < 1: - return '' - if page and page >= 1: - if ext: - return '/gist/%s/%s/?page=%s' % (name, ext, page) - else: - return '/gist/%s/?page=%s' % (name, page) - - -def make_page_args(request, name, ext=''): - page = request.get_form_var('page', 1) - start = 5 * (int(page) - 1) - link_prev = _make_links(name, int(page) - 1, ext=ext) - link_next = _make_links(name, int(page) + 1, ext=ext) - sort = request.get_form_var('sort', 'created') - direction = request.get_form_var('direction', 'desc') - return (page, start, link_prev, link_next, sort, direction) diff --git a/vilya/views/hub/__init__.py b/vilya/views/hub/__init__.py index c1bee6c..e21bfcf 100644 --- a/vilya/views/hub/__init__.py +++ b/vilya/views/hub/__init__.py @@ -20,14 +20,10 @@ _q_exports = ['teams', 'create', 'future', 'notification', 'my_pull_requests', 'remove', 'watch', 'unwatch', 'yours', 'my_issues', - 'public_timeline', 'search', 'emoji', 'bo', 'team', + 'public_timeline', 'search', 'emoji', 'team', 'add_team', 'beacon', 'shop', 'stat', 'chat', 'center'] -def bo(request): - return request.redirect('/shire_git_RO/commits/master/?author=bo') - - def teams(request): return TeamsUI()._q_index(request) diff --git a/vilya/views/j/__init__.py b/vilya/views/j/__init__.py deleted file mode 100644 index a41607f..0000000 --- a/vilya/views/j/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- - -_q_exports = ['pull', 'issue', 'more', 'chat', 'fav', 'hooks'] diff --git a/vilya/views/j/chat.py b/vilya/views/j/chat.py deleted file mode 100644 index 70ed116..0000000 --- a/vilya/views/j/chat.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- coding: utf-8 -*- - -from datetime import datetime -from vilya.libs.template import st -from vilya.models.message import get_room_message -from vilya.models.room import Room -from vilya.views.util import jsonize, render_message - -_q_exports = ['delete_room'] - - -@jsonize -def _q_lookup(request, room_name): - if request.method == "POST": - content = request.get_form_var('message') - author = request.user.username - date = datetime.now() - message_data = { - "content": content, - "author": author, - "date": date - } - room_message = get_room_message(room_name) - room_message.add_message(message_data) - return {'r': 1} - if request.method == "GET": - if room_name != 'lobby' and not Room.exists(room_name): - return {'r': 0, 'msg': 'room not exists'} - room_message = get_room_message(room_name) - messages = room_message.get_messages() - render_messages = [render_message(m) for m in messages] - return {'r': 1, 'msg': render_messages} - - -@jsonize -def delete_room(request): - room_name = request.get_form_var('room_name', '') - if Room.delete(room_name): - get_room_message(room_name).delete_by_key() - return {'r': 1, 'msg': '删除成功'} - - return {'r': 0, 'msg': '删除失败, 可能该room已被删除'} diff --git a/vilya/views/j/fav.py b/vilya/views/j/fav.py deleted file mode 100644 index 189e630..0000000 --- a/vilya/views/j/fav.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- - -from vilya.models.user_fav import UserFavItem -from vilya.models.consts import K_PULL, K_ISSUE -from vilya.views.util import jsonize - -_q_exports = [] - - -@jsonize -def _q_index(request): - user = request.user - tid = request.get_form_var('tid', '') - tkind = request.get_form_var('tkind', '0') - tkind = int(tkind) - if not user or tkind not in [K_PULL, K_ISSUE]: - return {'r': 1} - if request.method == "POST": - UserFavItem.add(user.username, tid, tkind) - elif request.method == 'DELETE': - UserFavItem.delete_by_user_target_kind(user.username, tid, tkind) - else: - return {'r': 1} - return {'r': 0} diff --git a/vilya/views/j/hooks.py b/vilya/views/j/hooks.py deleted file mode 100644 index 14a8fb4..0000000 --- a/vilya/views/j/hooks.py +++ /dev/null @@ -1,42 +0,0 @@ -# coding=utf-8 -from vilya.views.util import jsonize -from quixote.errors import TraversalError - -from vilya.models.project import CodeDoubanProject -from vilya.models.hook import CodeDoubanHook -from vilya.models.consts import TELCHAR_URL - -_q_exports = [] - - -def _q_lookup(request, proj_id): - if proj_id.isdigit(): - return JHookUI(proj_id) - raise TraversalError - - -class JHookUI(object): - _q_exports = ['telchar'] - - def __init__(self, proj_id): - self.proj_id = proj_id - - @jsonize - def telchar(self, request): - proj_id = self.proj_id - user = request.user - project = CodeDoubanProject.get(proj_id) - url = TELCHAR_URL - - if project.is_owner(user): - is_disable = request.get_form_var('close', '') - if is_disable: - hook = CodeDoubanHook.get_by_url(url) - if hook: - hook.destroy() - status = 0 - else: - CodeDoubanHook.add(url, proj_id) - status = 1 - return {'r': 0, 'status': status} - return {'r': 1} diff --git a/vilya/views/j/issue.py b/vilya/views/j/issue.py deleted file mode 100644 index d406232..0000000 --- a/vilya/views/j/issue.py +++ /dev/null @@ -1,93 +0,0 @@ -# -*- coding: utf-8 -*- - -from quixote.errors import TraversalError -from vilya.views.libs.template import st -from vilya.views.libs.text import parse_emoji, render_markdown_with_project, render_markdown -from vilya.views.models.issue import Issue -from vilya.views.models.project import CodeDoubanProject -from vilya.views.models.tag import TagName, Tag, TAG_TYPE_PROJECT_ISSUE, TAG_TYPE_TEAM_ISSUE -from vilya.views.models.team import Team -from vilya.views.util import jsonize - -_q_exports = ['delete_tag'] - - -def _q_lookup(request, uid): - if uid.isdigit(): - issue = Issue.get_cached_issue(uid) - if issue: - return IssueUI(issue) - raise TraversalError - - -@jsonize -def delete_tag(request): - # FIXME: ugly - if request.method == "POST": - user = request.user - if not user: - return {'r': 0, 'msg': '未登录,请先登录'} - tag_name = request.get_form_var('tag_name', '').decode('utf-8') - tag_type = request.get_form_var('tag_type', '') - tag_target_id = request.get_form_var('tag_target_id', '') - - if not tag_name: - return {'r': 0, 'msg': 'tag不能为空'} - try: - tag_type, tag_target_id = int(tag_type), int(tag_target_id) - except: - return {'r': 0, 'msg': '错误的数据类型'} - - if tag_type == TAG_TYPE_PROJECT_ISSUE: - target = CodeDoubanProject.get(tag_target_id) - elif tag_type == TAG_TYPE_TEAM_ISSUE: - target = Team.get(tag_target_id) - else: - return {'r': 0, 'msg': '错误的数据类型'} - - if not target.is_admin(user.name): - return {'r': 0, 'msg': '没有操作权限'} - - tname = TagName.get_by_name_and_target_id( - tag_name, tag_type, target.id) - if not tname: - return {'r': 0, 'msg': 'tag不存在'} - - tags = Tag.gets_by_tag_id(tname.id) - for tag in tags: - tag.delete() - tname.delete() - return {'r': 1, 'msg': '删除成功'} - - -class IssueUI(object): - _q_exports = ['edit', ] - - def __init__(self, issue): - self.target = issue.target - self.issue = issue - - @jsonize - def edit(self, request): - if request.method == 'POST': - title = request.get_form_var('title', '').decode('utf-8') - if not title.strip(): - return {'r': 1} - content = request.get_form_var('content', '').decode('utf-8') - user = request.user - user = user.name if user else None - if self.issue and user == self.issue.creator_id: - self.issue.update(title, content) - if self.issue == "project": - content_html = render_markdown_with_project( - content, self.target.name) - else: - content_html = render_markdown(content) - content_html += st('/widgets/markdown_checklist.html', - **locals()) - return {'r': 0, - 'title': title, - 'content': content, - 'title_html': parse_emoji(title), - 'content_html': content_html} - return {'r': 1} diff --git a/vilya/views/j/more/__init__.py b/vilya/views/j/more/__init__.py deleted file mode 100644 index ceb5e63..0000000 --- a/vilya/views/j/more/__init__.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- - -from quixote.errors import TraversalError -from vilya.views.util import jsonize, render_actions -from vilya.models.feed import get_public_feed, get_user_feed, get_user_inbox, PAGE_ACTIONS_COUNT - -_q_exports = ['pub', 'notify', 'team', 'userfeed'] - -class PubUI(object): - _q_exports = [] - - @jsonize - def _q_lookup(self, request, num): - if not num.isdigit(): - raise TraversalError - num = int(num) - actions = get_public_feed().get_actions(start=num, stop=num+PAGE_ACTIONS_COUNT-1) - length = len(actions) - render_html = render_actions(actions, show_avatar=True) - return {'result': render_html, 'length': length} - - -class FeedUI(object): - _q_exports = [] - - @jsonize - def _q_lookup(self, request, num): - user = request.user - if not user or not num.isdigit(): - raise TraversalError - num = int(num) - actions = get_user_inbox(user.username).get_actions(start=num, stop=num+PAGE_ACTIONS_COUNT-1) - length = len(actions) - render_html = render_actions(actions, show_avatar=True) - - return {'result': render_html, 'length': length} - - -class NotifyUI(object): - _q_exports = [] - - @jsonize - def _q_lookup(self, request, num): - if not num.isdigit(): - raise TraversalError - return {'r': 1} - - - -pub = PubUI() -userfeed = FeedUI() -notify = NotifyUI() diff --git a/vilya/views/j/more/team.py b/vilya/views/j/more/team.py deleted file mode 100644 index 4911a53..0000000 --- a/vilya/views/j/more/team.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- - -from quixote.errors import TraversalError -from vilya.views.util import jsonize, render_actions -from vilya.models.feed import get_team_feed, PAGE_ACTIONS_COUNT -from vilya.models.team import Team - -_q_exports = [] - -class TeamActionUI(object): - _q_exports = [] - - def __init__(self, team_uid): - self.team_uid = team_uid - - @jsonize - def _q_lookup(self, request, num): - if not num.isdigit(): - raise TraversalError - num = int(num) - team = Team.get_by_uid(self.team_uid) - actions = get_team_feed(team.id).get_actions(start=num, - stop=num+PAGE_ACTIONS_COUNT-1) - length = len(actions) - render_html = render_actions(actions, show_avatar=True) - return {'result': render_html, 'length': length} - - -def _q_lookup(request, team_uid): - return TeamActionUI(team_uid) diff --git a/vilya/views/j/pull.py b/vilya/views/j/pull.py deleted file mode 100644 index 14d5f58..0000000 --- a/vilya/views/j/pull.py +++ /dev/null @@ -1,48 +0,0 @@ -# -*- coding: utf-8 -*- - -from quixote.errors import TraversalError -from vilya.libs.template import st -from vilya.libs.text import parse_emoji, render_markdown_with_project -from vilya.models.ticket import Ticket -from vilya.models.project import CodeDoubanProject -from vilya.models.pull import PullRequest -from vilya.views.util import jsonize - -_q_exports = [] - - -def _q_lookup(request, uid): - if uid.isdigit(): - ticket = Ticket.get(uid) - if ticket: - return TicketUI(ticket) - raise TraversalError - - -class TicketUI(object): - _q_exports = ['edit', ] - - def __init__(self, ticket): - self.project = CodeDoubanProject.get(ticket.project_id) - self.proj_name = self.project.name - self.ticket_id = ticket.ticket_number - self.ticket = ticket - self.pullreq = PullRequest.get_by_proj_and_ticket( - self.project.id, self.ticket_id) - - @jsonize - def edit(self, request): - if request.method == 'POST': - title = request.get_form_var('title', '').decode('utf-8') - content = request.get_form_var('content', '').decode('utf-8') - user = request.user - user = user.name if user else None - if user == self.ticket.author: - self.ticket.update(title, content) - content_html = render_markdown_with_project( - content, self.proj_name) - content_html += st('/widgets/markdown_checklist.html', - **locals()) - return {'r': 0, 'title': title, 'content': content, - 'title_html': parse_emoji(title), 'content_html': content_html} - return {'r': 1} diff --git a/vilya/views/login.py b/vilya/views/login.py deleted file mode 100644 index 68eb270..0000000 --- a/vilya/views/login.py +++ /dev/null @@ -1,24 +0,0 @@ -# coding=utf-8 -import json - -from vilya.libs.template import st -from vilya.models.user import User, set_user - -_q_exports = [] - - -def _q_index(request): - if request.method == 'POST': - name = request.get_form_var('username') - password = request.get_form_var('password') - user = User.get_by_name(name) - if user and user.validate_password(password): - continue_url = request.get_form_var( - 'continue', '') or request.get_form_var('Referer', '') - request.user = user - set_user(user.id) - return json.dumps({"r": 0, "continue": continue_url or "/"}) - else: - message = '用户名或密码错误!' - return json.dumps({"r": 1, 'message': message}) - return st('login.html') diff --git a/vilya/views/logout.py b/vilya/views/logout.py deleted file mode 100644 index ac1b55e..0000000 --- a/vilya/views/logout.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import -from quixote import get_session_manager - -from vilya.libs.template import st - -_q_exports = [] - - -def __call__(request): - return _q_index(request) - - -def _q_index(request): - user = request.user - if request.method == 'POST': - if user: - continue_url = request.get_form_var( - 'continue', '') or request.get_form_var('Referer', '') - get_session_manager().expire_session(request) - return request.redirect(continue_url or '/') - context = {} - context['current_user'] = user - return st('logout.html', **context) diff --git a/vilya/views/m.py b/vilya/views/m.py deleted file mode 100644 index 8fb0eec..0000000 --- a/vilya/views/m.py +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import absolute_import - -from vilya.libs.template import st -from vilya.models.feed import get_user_inbox, get_public_feed - -MAX_ACT_COUNT = 100 - - -_q_exports = ['actions', 'public_timeline'] - - -def _q_index(request): - return st('/m/feed.html', **locals()) - - -def public_timeline(request): - is_public = True - return st('/m/feed.html', **locals()) - - -def actions(request): - since_id = request.get_form_var('since_id', '') - is_public = request.get_form_var('is_public', '') - user = request.user - all_actions = [] - if is_public == 'true': - all_actions = get_public_feed().get_actions(0, MAX_ACT_COUNT) - elif user: - all_actions = get_user_inbox(user.username).get_actions( - 0, MAX_ACT_COUNT) - if since_id: - actions = [] - for action in all_actions: - if action.get('uid') == since_id: - break - actions.append(action) - else: - actions = all_actions - return st('/m/actions.html', **locals()) diff --git a/vilya/views/mirrors.py b/vilya/views/mirrors.py deleted file mode 100644 index 4b34487..0000000 --- a/vilya/views/mirrors.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- coding: utf-8 -*- -from vilya.libs.template import st -from vilya.models.project import CodeDoubanProject - - -_q_exports = [] - - -def _q_index(request): - your_projects = CodeDoubanProject.get_projects( - owner="mirror", sortby='sumup') - return st('/mirrors.html', **locals()) diff --git a/vilya/views/page.py b/vilya/views/page.py deleted file mode 100644 index 622acd8..0000000 --- a/vilya/views/page.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import absolute_import - -from vilya.libs.template import st - -_q_exports = ['promo_proj'] - - -def promo_proj(request): - promo_projs = { - "Vagrant": { - "image": "http://t.douban.com/view/status/raw/public/82670450897abd1.jpg", # noqa - "title": "Shire in Vagrant", - "description": u"搭建开发环境
    从未如此轻松", - "url": "http://theoden.intra.douban.com:45068/siv/index.html", - }, - "code": { - "image": "http://img3.douban.com/mpic/s10482437.jpg", - "title": "Code", - "description": u"Code开发者招募中", - "url": "http://code.dapps.douban.com/code", - }, - "codecli": { - "image": "http://pastebin.dapps.douban.com/image/86/6386_raw.png", - "title": "codecli", - "description": u"codecli 帮你玩转 pullreq", - "url": "http://code.dapps.douban.com/codecli", - }, - "MoGit": { - "image": "http://pastebin.dapps.douban.com/image/30/6930_raw.png", - "title": "MoGit", - "description": u"设计师也能享受Code", - "url": "http://code.dapps.douban.com/bear/mogit/docs/pages/index.html", # noqa - }, - "P": { - "image": "http://p.dapps.douban.com/i/0d433de4ae8211e2b5e424b6fdf76328.png", # noqa - "title": "P", - "description": u"快速简单的分享图片", - "url": "http://p.dapps.douban.com/", - }, - } - - from random import sample - promo_proj = promo_projs[sample(promo_projs.keys(), 1)[0]] - return st('page/promo_proj.html', **locals()) diff --git a/vilya/views/people.py b/vilya/views/people.py deleted file mode 100644 index 7be1bb8..0000000 --- a/vilya/views/people.py +++ /dev/null @@ -1,169 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import absolute_import - -from quixote.errors import TraversalError -import time -from vilya.libs.template import st -from vilya.libs.validators import check_user_id -import itertools -from datetime import datetime, timedelta -import dateutil.parser - -from vilya.views.util import jsonize -from vilya.models.project import CodeDoubanProject -from vilya.models.user import User -from vilya.models.badge import Badge -from vilya.models.recommendation import Recommendation -from vilya.models.contributions import UserContributions -from vilya.models.feed import get_user_feed -from vilya.models.team import Team -from vilya.models.ticket import Ticket -from vilya.models.user import judge_user - -_q_exports = [] - - -def _q_lookup(request, name): - # FIXME: check user name. - if judge_user(name) == "team": - return request.redirect('/teams/%s/' % name) - else: - return UserUI(request, name) - - -class UserUI: - _q_exports = ['badges', 'follow', 'unfollow', 'following', 'followers', - 'add_rec', 'praises', 'contributions', 'contribution_detail'] - - FOLLOW_LIST_USER_COUNT = 24 - - def __init__(self, request, name): - self.name = name - - def _q_access(self, request): - if not check_user_id(self.name): - raise TraversalError('not a valid user id') - - def _q_index(self, request): - name = self.name - your_projects = CodeDoubanProject.get_projects(owner=name, - sortby='lru') - actions = get_user_feed(name).get_actions(0, 20) - user = User(name) - teams = Team.get_by_user_id(user.name) - badge_items = user.get_badge_items() - followers_count = user.followers_count - following_count = user.following_count - if user and user.username == name and user.get_new_badges(): - user.clear_new_badges() - return st('people.html', **locals()) - - @jsonize - def follow(self, request): - name = self.name - user = request.user - if not user: - return {"action": 0, "msg": "Please login first."} - username = user.username - if username == name: - action = 0 - msg = "You can't follow yourself" - else: - user.follow(name) - action = 1 - msg = "success" - return {"action": action, "msg": msg} - - @jsonize - def unfollow(self, request): - name = self.name - user = request.user - if not user: - return {"action": 0, "msg": "Please login first."} - username = user.username - if username == name: - action = 0 - msg = "You can't unfollow yourself" - else: - User(username).unfollow(name) - action = -1 - msg = "success" - return {"action": action, "msg": msg} - - def following(self, request): - name = self.name - list_type = "following" - user_list = User(name).get_following() - return self.follow_list(request, list_type, user_list) - - def followers(self, request): - name = self.name - list_type = "followers" - user_list = User(name).get_followers() - return self.follow_list(request, list_type, user_list) - - def follow_list(self, request, list_type, user_list): - name = self.name - page = int(request.get_form_var('page', 1)) - count = len(user_list) - start = self.FOLLOW_LIST_USER_COUNT * (page - 1) - n_pages = count / self.FOLLOW_LIST_USER_COUNT + 1 - user_list = user_list[start:(start + self.FOLLOW_LIST_USER_COUNT)] - current_user = user = request.user - current_user_following = current_user.get_following() \ - if current_user else [] - return st('follow-list.html', **locals()) - - def badges(self, request): - name = self.name - badge_items = Badge.get_badge_items() - _date_badge_items = [i for i in badge_items if i.date] - items_groupby_date = itertools.groupby( - _date_badge_items, lambda badge_item: badge_item.date.date()) - today = datetime.now().date() - yesterday = today - timedelta(days=1) - return st('badge/timeline.html', **locals()) - - def add_rec(self, request): - name = self.name - if request.method == "POST": - content = request.get_form_var('content') - user = request.user - r = Recommendation.add( - from_user=user.name, to_user=self.name, content=content) - return request.redirect('/people/%s/praises' % name) - return st('people_add_rec.html', **locals()) - - def praises(self, request): - name = self.name - recs = Recommendation.gets_by_user(name) - return st('people_recs.html', **locals()) - - @jsonize - def contributions(self, request): - res = {} - for date_, contribution in UserContributions.get_by_user( - self.name).iteritems(): - timestamp = time.mktime( - datetime.strptime(date_, '%Y-%m-%d').timetuple()) - score = contribution[0] - res.setdefault(timestamp, score) - return res - - def contribution_detail(self, request): - req_date = request.get_form_var('date') - if req_date: - try: - req_date = dateutil.parser.parse( - req_date).astimezone(dateutil.tz.tzoffset('EST', 8*3600)) - except ValueError as e: - return "" - contributions = UserContributions.get_by_date(self.name, req_date) - owned = contributions.get('owned_tickets') - commented = contributions.get('commented_tickets') - owned_tickets = filter(None, [Ticket.get(id_) for id_ in owned]) - commented_tickets = filter(None, [Ticket.get(comment[0]) - for comment in commented]) - return st('people_contribution_detail.html', **locals()) - return "" diff --git a/vilya/views/praise.py b/vilya/views/praise.py deleted file mode 100644 index 9b80425..0000000 --- a/vilya/views/praise.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import absolute_import - -import json - -from vilya.libs.template import st -from vilya.models.recommendation import Recommendation - -_q_exports = ['vote'] - - -def _q_index(request): - """recommendations timeline""" - start = request.get_form_var('start') - start = start and start.isdigit() and int(start) or 0 - limit = 20 - recs = Recommendation.gets(start=start, limit=limit) - return st('recommendations.html', **locals()) - - -def vote(request): - user = request.user - if user: - rec_id = request.get_form_var('rec_id') - r = Recommendation.get(rec_id) - if r: - r.add_vote(user.name) - return json.dumps({'r': 1}) - return json.dumps({'r': 0}) diff --git a/vilya/views/register.py b/vilya/views/register.py deleted file mode 100644 index 335435b..0000000 --- a/vilya/views/register.py +++ /dev/null @@ -1,23 +0,0 @@ -# coding=utf-8 -import json - -from vilya.libs.template import st -from vilya.models.nuser import User2 as CodeUser -from vilya.models.user import set_user - -_q_exports = [] - - -def _q_index(request): - if request.method == 'POST': - email = request.get_form_var('email') - password = request.get_form_var('password') - if not email: - return json.dumps({'r': 1, 'message': 'Email没有指定'}) - user_name = email.split('@')[0] - if CodeUser.is_exists(user_name): - return json.dumps({'r': 1, 'message': '用户已存在'}) - code_user = CodeUser.add(user_name, password) - set_user(code_user.id) - return json.dumps({'r': 0}) - return st('register.html') diff --git a/vilya/views/settings/__init__.py b/vilya/views/settings/__init__.py deleted file mode 100644 index 887a297..0000000 --- a/vilya/views/settings/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import absolute_import - -from vilya.libs.auth.decorators import login_required -from vilya.models.user import User - -_q_exports = ['emails', 'github', 'notification', 'ssh', 'codereview'] - - -@login_required -def _q_index(request): - return request.redirect("/settings/emails") - - -def _q_access(request): - user = request.user - if not user: - return request.redirect(User.create_login_url(request.url)) diff --git a/vilya/views/settings/codereview.py b/vilya/views/settings/codereview.py deleted file mode 100644 index 794c105..0000000 --- a/vilya/views/settings/codereview.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- - -import json -from vilya.libs.auth.decorators import login_required -from vilya.libs.template import st - -_q_exports = ['setting', ] - - -@login_required -def _q_index(request): - user = request.user - return st('settings/codereview.html', **locals()) - - -@login_required -def setting(request): - is_enable = request.get_form_var('is_enable') - field = request.get_form_var('field') - user = request.user - result = "success" - origin = user.settings.__getattr__(field) - try: - user.settings.__setattr__(field, is_enable) - except Exception: - result = "fail" - return json.dumps({"result": result, "origin": origin}) diff --git a/vilya/views/settings/emails.py b/vilya/views/settings/emails.py deleted file mode 100644 index 19aa93f..0000000 --- a/vilya/views/settings/emails.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import absolute_import - -from vilya.libs.auth.decorators import login_required -from vilya.libs.template import st -from vilya.models.user import CodeDoubanUserEmails - -_q_exports = [] - - -@login_required -def _q_index(request): - errors = [] - user = request.user - emails = user.emails - if request.method == "POST": - email = request.get_form_var('email') - errors = CodeDoubanUserEmails.validate(user.name, email) - if not errors: - CodeDoubanUserEmails.add(user.name, email) - return request.redirect('/settings/emails') - return st('/settings/emails.html', **locals()) - - -@login_required -def _q_lookup(request, email_id): - user = request.user - return EmailUI(user, email_id) - - -class EmailUI(object): - _q_exports = ['delete', 'set_notif', 'un_notif'] - - def __init__(self, user, email_id): - self.user = user - self.email_id = email_id - self.user_email = CodeDoubanUserEmails.check_own_by_user(user.name, - email_id) - - def delete(self, request): - if self.user_email: - self.user_email.delete() - return request.redirect('/settings/emails') - - def set_notif(self, request): - if self.user_email: - addr = self.user_email.email - self.user.settings.notif_other_emails \ - = self.user.settings.notif_other_emails + [addr] - return request.redirect('/settings/emails') - - def un_notif(self, request): - if self.user_email: - addr = self.user_email.email - self.user.settings.notif_other_emails = [ - e for e in self.user.settings.notif_other_emails - if e != addr] - return request.redirect('/settings/emails') diff --git a/vilya/views/settings/github.py b/vilya/views/settings/github.py deleted file mode 100644 index 918bc43..0000000 --- a/vilya/views/settings/github.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import absolute_import - -from vilya.libs.auth.decorators import login_required -from vilya.libs.template import st -from vilya.models.user import CodeDoubanUserGithub - -_q_exports = [] - - -@login_required -def _q_index(request): - errors = [] - user = request.user - githubs = user.githubs - if request.method == "POST": - user_name = request.get_form_var('github') - errors = CodeDoubanUserGithub.validate(user.name, user_name) - if not errors: - CodeDoubanUserGithub.add(user.name, user_name) - return request.redirect('/settings/github') - return st('/settings/github.html', **locals()) - - -@login_required -def _q_lookup(request, github_id): - if request.get_form_var('_method') == 'delete': - user = request.user - github = CodeDoubanUserGithub.check_own_by_user(user.name, github_id) - if github: - github.delete() - return request.redirect('/settings/github') diff --git a/vilya/views/settings/notification.py b/vilya/views/settings/notification.py deleted file mode 100644 index c8d4f2e..0000000 --- a/vilya/views/settings/notification.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- - -import json -from vilya.libs.auth.decorators import login_required -from vilya.libs.template import st - -_q_exports = ['setting', ] - - -@login_required -def _q_index(request): - user = request.user - return st('settings/notification.html', **locals()) - - -@login_required -def setting(request): - is_on = request.get_form_var('is_on') - notifications_meta = request.get_form_var('notifications_meta') - user = request.user - result = "success" - try: - user.settings.__setattr__(notifications_meta, is_on) - except Exception: - result = "fail" - return json.dumps({"result": result}) diff --git a/vilya/views/settings/ssh.py b/vilya/views/settings/ssh.py deleted file mode 100644 index 289e64e..0000000 --- a/vilya/views/settings/ssh.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import absolute_import - -from vilya.libs.auth.decorators import login_required -from vilya.libs.template import st -from vilya.models.sshkey import SSHKey - -_q_exports = [] - - -@login_required -def _q_index(request): - errors = [] - key_lines = '' - user = request.user - sshkeys = user.sshkeys - if request.method == "POST": - key_lines = request.get_form_var('ssh') - newsshkeys = [] - errorkeys = [] - for index, line in enumerate(key_lines.splitlines()): - valid = SSHKey.validate(user.name, line) - if not valid: - errorkeys.append((index, line)) - continue - duplicated = SSHKey.is_duplicated(user.name, line) - if duplicated: - errorkeys.append((index, line)) - continue - newsshkeys.append(line) - - if not errorkeys: - for key in newsshkeys: - SSHKey.add(user.name, key) - return request.redirect('/settings/ssh') - - error_prefix = 'Please check your SSH Key, Line: ' - for no, key in errorkeys: - error = error_prefix + '%s ' % no - errors.append(error) - return st('/settings/ssh.html', **locals()) - - -@login_required -def _q_lookup(request, sshkey_id): - if request.get_form_var('_method') == 'delete': - user = request.user - sshkey = SSHKey.check_own_by_user(user.name, sshkey_id) - if sshkey: - sshkey.delete() - return request.redirect('/settings/ssh') diff --git a/vilya/views/uis/archive.py b/vilya/views/uis/archive.py deleted file mode 100644 index 18c87a6..0000000 --- a/vilya/views/uis/archive.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import absolute_import -from quixote.errors import TraversalError -from vilya.models.project import CodeDoubanProject - - -class ArchiveUI(object): - _q_exports = [] - - def __init__(self, proj_name): - project = CodeDoubanProject.get_by_name(proj_name) - self.project = project - - def _q_lookup(self, request, part): - repo = self.project.repo - sha = repo.sha(part) - if not sha: - raise TraversalError - ext = request.get_form_var('ext') - if ext == 'tar': - request.response.set_content_type("application/x-tar") - request.response.set_header("Content-Disposition", - "filename=%s.tar" % part) - return repo.archive(part, ref=sha, ext=ext) - elif ext == 'tar.gz': - request.response.set_content_type("application/x-gzip") - request.response.set_header("Content-Disposition", - "filename=%s.tar.gz" % part) - return repo.archive(part, ref=sha) - request.response.set_content_type("application/x-gzip") - request.response.set_header("Content-Disposition", - "filename=%s.tar.gz" % part) - return repo.archive(part, ref=sha) diff --git a/vilya/views/uis/browsefiles.py b/vilya/views/uis/browsefiles.py deleted file mode 100644 index 6f32553..0000000 --- a/vilya/views/uis/browsefiles.py +++ /dev/null @@ -1,64 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import absolute_import - -import json -from vilya.libs.template import st -from vilya.models.project import CodeDoubanProject - -_q_exports = [] - - -class BrowsefilesUI: - _q_exports = ['setting'] - - def __init__(self, proj_name): - self.proj = proj_name - - def _q_access(self, request): - if 'json' in request.environ['HTTP_ACCEPT']: - self.output = 'json' - else: - self.output = 'html' - - def _q_index(self, request): - project = CodeDoubanProject.get_by_name(self.proj) - user = request.user - path = request.get_form_var('path', '') - rev = request.get_form_var('rev', project.default_branch) - allfiles = project.repo.get_tree(rev, path=path) - allfiles = [_add_file_type_and_warns(f) for f in allfiles] - errors = '' - project_name = self.proj - project = CodeDoubanProject.get_by_name(project_name) - ref = rev - if ref is None: - ref = project.default_branch - branches = project.repo.branches - tags = project.repo.tags - ref_type = 'branch' if ref in branches else 'tag' \ - if ref in tags else 'tree' - if self.output == 'json': - return json.dumps(allfiles) - else: - return st('browsefiles.html', **locals()) - - -def _add_file_type_and_warns(node): - code_file_exts = 'py rb c h html mako ptl js css less handlebars coffee sql'.split() # noqa - bad_exts = 'pyc exe'.split() - node_ext = node['path'].rsplit('.')[1] if '.' in node['path'] else '' - if node['type'] == 'tree': - icon_type = 'directory' - elif node['type'] == 'commit': - icon_type = 'submodule' - elif node_ext in code_file_exts: - icon_type = 'code-file' - else: - icon_type = 'text-file' - node['icon-type'] = icon_type - if node_ext in bad_exts: - node['warn'] = 'bad' - else: - node['warn'] = 'no' - return node diff --git a/vilya/views/uis/code_review.py b/vilya/views/uis/code_review.py deleted file mode 100644 index 69d9192..0000000 --- a/vilya/views/uis/code_review.py +++ /dev/null @@ -1,53 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import absolute_import -from quixote.errors import TraversalError - -from vilya.views.util import jsonize, http_method -from vilya.models.linecomment import PullLineComment -from vilya.models.project import CodeDoubanProject -from vilya.libs.template import st - -_q_exports = [] - - -class CodeReviewUI(object): - - _q_exports = ['delete', 'edit'] - - def __init__(self, proj_name): - self.proj_name = proj_name - self.code_review = None - - def _q_lookup(self, request, comment_id): - comment = PullLineComment.get(comment_id) - if not comment: - raise TraversalError( - "Unable to find comment %s" % comment_id) - else: - self.code_review = comment - return self - - @jsonize - def delete(self, request): - user = request.user - if self.code_review.author == user.name: - ok = self.code_review.delete() - if ok: - return {'r': 1} # FIXME: 这里 r=1 表示成功,跟其他地方不统一 - return {'r': 0} - - @jsonize - @http_method(methods=['POST']) - def edit(self, request): - user = request.user - project = CodeDoubanProject.get_by_name(self.proj_name) - content = request.get_form_var( - 'pull_request_review_comment', '').decode('utf-8') - if self.code_review.author == user.name: - self.code_review.update(content) - linecomment = PullLineComment.get(self.code_review.id) - pullreq = True - return dict( - r=0, html=st('/pull/ticket_linecomment.html', **locals())) - return dict(r=1) diff --git a/vilya/views/uis/comments.py b/vilya/views/uis/comments.py deleted file mode 100644 index 65e50aa..0000000 --- a/vilya/views/uis/comments.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import absolute_import - -from quixote.errors import TraversalError - -from vilya.models.project import CodeDoubanProject -from vilya.models.comment import Comment, latest -from vilya.views.util import http_method - -_q_exports = [] - - -class CommentUI: - ''' project commit comment ui ''' - _q_exports = ['new'] - - def __init__(self, proj_name): - self.proj_name = proj_name - - def _q_index(self, request): - return "Last comments list TODO" + str(latest()) - - @http_method(methods=['POST']) - def new(self, request): - project = CodeDoubanProject.get_by_name(self.proj_name) - user = request.user - ref = request.get_form_var('ref') - assert ref, "comment ref cannot be empty" - content = request.get_form_var('content', '') - new_comment = Comment.add(project, ref, user.name, content) - - return request.redirect("/%s/commit/%s#%s" % - (self.proj_name, ref, new_comment.uid)) - - def _q_lookup(self, request, comment_id): - if request.method == 'DELETE': - # FIXME: 不用验证user? - ok = Comment.delete(comment_id) - if not ok: - raise TraversalError( - "Unable to delete comment %s" % comment_id) - return '' - return "Display comment %s TODO" % comment_id diff --git a/vilya/views/uis/compare.py b/vilya/views/uis/compare.py deleted file mode 100644 index 58afbb8..0000000 --- a/vilya/views/uis/compare.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import absolute_import - -from quixote.errors import TraversalError -from vilya.libs.template import st -from vilya.models.project import CodeDoubanProject -from itertools import groupby -from vilya.models.comment import Comment - - -class CompareUI(object): - - _q_exports = [] - - def __init__(self, proj_name): - self.proj_name = proj_name - project = CodeDoubanProject.get_by_name(proj_name) - self.project = project - - def _q_index(self, request): - return TraversalError( - 'please provide valid start & end revisions: /compare/start...end') - - def _q_lookup(self, request, revrange): - current_user = request.user - try: - sha1, sha2 = revrange.split('...') - except ValueError: - raise TraversalError( - 'please provide valid start & end revisions: /compare/sha1...sha2') # noqa - project = self.project - commits = project.repo.get_commits(sha2, sha1) - if commits is False: - raise TraversalError() - lasttime = commits and commits[0].author_time.strftime( - "%Y-%m-%d %H:%M:%S") or 'UNKNOWN' - grouped_commits = groupby(commits, lambda c: c.author_time.date()) - n_commits = len(commits) - n_authors = len(set(c.author.username for c in commits)) - diff = project.repo.get_diff(sha2, - from_ref=sha1, - rename_detection=True) - #diffs = project.git.get_3dot_diff(sha1, sha2) - n_files = diff.length if diff else 0 - comments = [] - for ci in commits: - comments.extend(Comment.gets_by_proj_and_ref(project.id, ci.sha)) - branches = project.repo.branches - tags = project.repo.tags - ref = project.default_branch - n_comments = len(comments) - ref_type = 'branch' if ref in branches else 'tag' \ - if ref in tags else 'tree' - return st('compare.html', **locals()) diff --git a/vilya/views/uis/conf.py b/vilya/views/uis/conf.py deleted file mode 100644 index ed68aa4..0000000 --- a/vilya/views/uis/conf.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import absolute_import - -from vilya.libs.template import st -from vilya.models.project import CodeDoubanProject - -_q_exports = [] - - -class SettingsConfUI: - _q_exports = [] - - def __init__(self, proj_name): - self.proj_name = proj_name - - def _q_index(self, request): - user = request.user - tdt = { - 'user': user, - 'project': CodeDoubanProject.get_by_name(self.proj_name), - 'request': request, - } - return st('settings/config.html', **tdt) diff --git a/vilya/views/uis/hooks.py b/vilya/views/uis/hooks.py deleted file mode 100644 index fa5a702..0000000 --- a/vilya/views/uis/hooks.py +++ /dev/null @@ -1,61 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import absolute_import - -from vilya.libs.template import st - -from vilya.models.project import CodeDoubanProject -from vilya.models.hook import CodeDoubanHook -from vilya.models.consts import FEATURE_HOOK_URLS, TELCHAR_URL - -_q_exports = [] - - -class HookUI: - _q_exports = ['new'] - - def __init__(self, proj_name): - self.proj_name = proj_name - - def _q_index(self, request): - errors = '' - project_name = self.proj_name - user = request.user - project = CodeDoubanProject.get_by_name(project_name) - hooks = [hook for hook in project.hooks - if hook.url not in FEATURE_HOOK_URLS] - enabled_telchar = next((hook for hook in project.hooks - if hook.url == TELCHAR_URL), False) - return st('settings/hooks.html', **locals()) - - def new(self, request): - errors = '' - project_name = self.proj_name - user = request.user - url = request.get_form_var('url') - project = CodeDoubanProject.get_by_name(project_name) - hooks = project.hooks - if request.method == "POST": - hook = CodeDoubanHook(0, url, project.id) - exists_id = CodeDoubanHook.get_id_by_url(project.id, url) - errors = hook.validate() - if not project.is_owner(user): - errors.append("You can't set hooks for this project") - if exists_id is not None: - errors.append("This hook url has exists") - if not errors: - CodeDoubanHook.add(hook.url, hook.project_id) - return request.redirect('/%s/settings/hooks' % project_name) - - return st('settings/hooks.html', **locals()) - - def _q_lookup(self, request, hook_id): - project_name = self.proj_name - user = request.user - project = CodeDoubanProject.get_by_name(project_name) - if request.get_form_var('_method') == 'delete' \ - and project.is_owner(user): - hooks = project.hooks - hook = (h for h in hooks if int(h.id) == int(hook_id)).next() - hook.destroy() - return request.redirect('/%s/settings/hooks' % project_name) diff --git a/vilya/views/uis/settings.py b/vilya/views/uis/settings.py deleted file mode 100644 index 1bacc9e..0000000 --- a/vilya/views/uis/settings.py +++ /dev/null @@ -1,176 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import absolute_import -from quixote.errors import AccessError, TraversalError -from vilya.libs.template import st -from vilya.models.user import User -from vilya.models.project import CodeDoubanProject -from vilya.models.sphinx_docs import SphinxDocs -from vilya.models.team import Team -from vilya.models.team_group import TeamGroup -from tasks import sphinx_builds_add -from vilya.views.uis.hooks import HookUI -from vilya.views.uis.conf import SettingsConfUI - - -_q_exports = [] - - -class SettingsUI(object): - - _q_exports = ['add_committer', 'del_committer', 'sphinx_docs', 'hooks', - 'conf', 'pages', 'transfer_project', 'rename_project', - 'groups'] - - def __init__(self, proj_name): - self.proj_name = proj_name - self.project = CodeDoubanProject.get_by_name(self.proj_name) - if not self.project: - raise TraversalError - - def _q_access(self, request): - self.user = request.user - if not self.project.is_owner(self.user): - raise AccessError - - def _q_index(self, request): - user = request.user - project = self.project - - if request.method == 'GET': - teams = Team.gets() - owner = User(self.project.owner_id) - committers = project.get_committers_by_project(project.id) - - if project.fork_from: - fork_from = CodeDoubanProject.get(project.fork_from) - - return st('settings/main.html', **locals()) - - elif request.method == 'POST': - if user.name == project.owner_id: - summary = request.get_form_var('summary', '') - product = request.get_form_var('product', '') - intern_banned = request.get_form_var('intern_banned', None) - project.update(summary, product, self.proj_name, intern_banned) - - return request.redirect('/%s/settings' % self.proj_name) - - def committer(self, request, action): - user = request.user - project = self.project - - if request.method == 'POST': - if user.name == project.owner_id: - committers = request.get_form_var('username', '') - if action == 'add': - committers = committers.split(' ') - for committer in committers: - project.add_committer(project.id, committer) - elif action == 'del': - project.del_committer(project.id, committers) - - def add_committer(self, request): - self.committer(request, 'add') - return request.redirect('/%s/settings' % self.proj_name) - - def del_committer(self, request): - self.committer(request, 'del') - return request.redirect('/%s/settings' % self.proj_name) - - def sphinx_docs(self, request): - user = request.user - docs = SphinxDocs(self.proj_name) - if request.get_form_var('force_rebuild') == 'mq': - sphinx_builds_add(self.proj_name) - return request.redirect( - '/%s/settings/sphinx_docs' % self.proj_name) - if request.get_form_var('force_rebuild') == 'direct': - docs.build_all() - return request.redirect( - '/%s/settings/sphinx_docs' % self.proj_name) - tdt = { - 'project': CodeDoubanProject.get_by_name(self.proj_name), - 'request': request, - 'enabled': docs.enabled, - 'last_build': docs.last_build_info(), - 'user': user, - } - return st('settings/sphinx_docs.html', **tdt) - - @property - def hooks(self): - return HookUI(self.proj_name) - - @property - def conf(self): - return SettingsConfUI(self.proj_name) - - def pages(self, request): - user = request.user - docs = SphinxDocs(self.proj_name) - tdt = { - 'project': CodeDoubanProject.get_by_name(self.proj_name), - 'request': request, - 'user': user, - 'docs': docs, - 'last_build': docs.last_build_info(), - } - return st('settings/pages.html', **tdt) - - def transfer_project(self, request): - user_id = request.get_form_var('username') - if user_id: - self.project.transfer_to(user_id) - return 'transfer success' - return 'please input a username' - - def rename_project(self, request): - repo_name = request.get_form_var('repo_name') - if repo_name: - if self.project.rename(repo_name) is not False: - return request.redirect(self.project.url) - else: - return "repo name already exist" - return 'please input a repo name' - - @property - def groups(self): - return SettingsGroupsUI(self.project) - - -class SettingsGroupsUI(object): - _q_exports = ['destroy'] - - def __init__(self, project): - self.project = project - - def _q_index(self, request): - project = self.project - group_name = request.get_form_var('group', '') - if not group_name: - return request.redirect("%ssettings/" % project.url) - team, _, group = group_name.rpartition('/') - t = Team.get_by_uid(team) - if not t: - return request.redirect("%ssettings/" % project.url) - g = TeamGroup.get(team_id=t.id, name=group) - if not g: - return request.redirect("%ssettings/" % project.url) - g.add_project(project_id=project.id) - return request.redirect("%ssettings/" % project.url) - - def destroy(self, request): - project = self.project - group_name = request.get_form_var('group', '') - if not group_name: - return request.redirect("%ssettings/" % project.url) - team, _, group = group_name.rpartition('/') - t = Team.get_by_uid(team) - if not t: - return request.redirect("%ssettings/" % project.url) - g = TeamGroup.get(team_id=t.id, name=group) - if not g: - return request.redirect("%ssettings/" % project.url) - g.remove_project(project_id=project.id) - return request.redirect("%ssettings/" % project.url) diff --git a/vilya/views/uis/watchers.py b/vilya/views/uis/watchers.py deleted file mode 100644 index e2aef27..0000000 --- a/vilya/views/uis/watchers.py +++ /dev/null @@ -1,39 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import absolute_import - -from vilya.libs.template import st -from vilya.models.project import CodeDoubanProject - - -class WatchersUI(object): - _q_exports = [] - - template = 'watchers.html' - - def __init__(self, proj_name): - self.proj_name = proj_name - self.project = CodeDoubanProject.get_by_name(proj_name) - - def _q_index(self, request): - project = self.project - user = request.user - users = self.get_users() - projects = self.get_projects() - return st(self.template, **locals()) - - def get_users(self): - return self.project.get_watch_users() - - def get_projects(self): - return [] - - -class ForkersUI(WatchersUI): - template = 'forkers.html' - - def get_users(self): - return self.project.get_forked_users() - - def get_projects(self): - return self.project.get_forked_projects() diff --git a/vilya/views/watch.py b/vilya/views/watch.py deleted file mode 100644 index 06419c7..0000000 --- a/vilya/views/watch.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import absolute_import -import json - -from vilya.models.project import CodeDoubanProject -from vilya.views.util import require_login_json, error_message - -_q_exports = [] - - -def _q_index(request): - """get all watches""" - user = request.user - if user: - return json.dumps([ - {'pid': str(p.id)} - for p in CodeDoubanProject.get_watched_projects_by_user(user.name)]) # noqa - return '' - - -def _q_lookup(request, proj_id): - if request.method == "POST": - return new(request, proj_id) - elif request.method == "DELETE": - return remove(request, proj_id) - elif request.method == "GET": - return has_watched(request, proj_id) - else: - return error_message("bad request") - - -@require_login_json -def new(request, proj_id): - user = request.user - CodeDoubanProject.add_watch(proj_id, user.name) - return json.dumps({"ok": 1}) - - -@require_login_json -def remove(request, proj_id): - user = request.user - CodeDoubanProject.del_watch(proj_id, user.name) - return json.dumps({"ok": 1}) - - -# FIXME: ugly fix -def has_watched(request, proj_id): - user = request.user - if CodeDoubanProject.has_watched(proj_id, user.name): - return json.dumps({"ok": 1}) - return json.dumps({"ok": 0}) diff --git a/vilya/views/watching.py b/vilya/views/watching.py deleted file mode 100644 index 6869a53..0000000 --- a/vilya/views/watching.py +++ /dev/null @@ -1,14 +0,0 @@ -# -*- coding: utf-8 -*- - -from vilya.libs.template import st - - -_q_exports = [] - - -def _q_index(request): - user = request.user - if user: - watched_projects = user.watched_projects - return st('/watching.html', **locals()) - return request.redirect("/hub/teams") diff --git a/vilya/wsgi.py b/vilya/wsgi.py new file mode 100644 index 0000000..a88bb10 --- /dev/null +++ b/vilya/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for vilya project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "vilya.settings") + +application = get_wsgi_application()