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 @@