├── .gitignore ├── .travis.yml ├── README.md ├── __init__.py ├── actions ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── tests.py ├── utils.py └── views.py ├── api ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ └── __init__.py ├── models.py ├── tests.py └── views.py ├── blog ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20160514_0813.py │ ├── 0003_auto_20161008_1137.py │ └── __init__.py ├── models.py ├── tests.py └── views.py ├── comments ├── __init__.py ├── admin.py ├── apps.py ├── forms.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20160514_0738.py │ ├── 0003_auto_20160514_0738.py │ ├── 0004_auto_20160528_0533.py │ ├── 0005_auto_20161003_1730.py │ └── __init__.py ├── models.py ├── tests.py └── views.py ├── conf ├── gunicorn_conf.py └── supervisord.conf ├── constants └── __init__.py ├── data ├── data.json └── location.json ├── dev ├── commonJSFile │ ├── ajax.js │ ├── form.js │ └── preview.js ├── css │ ├── common.less │ ├── food │ │ ├── create.less │ │ ├── detail.less │ │ ├── explore.less │ │ └── list.less │ ├── home │ │ └── index.less │ ├── topic │ │ ├── detail.less │ │ └── list.less │ └── user │ │ ├── base.less │ │ ├── food_list.less │ │ ├── index.less │ │ ├── list.less │ │ ├── login_register.less │ │ ├── profile.less │ │ ├── settings.less │ │ ├── share.less │ │ └── topic_collection.less ├── js │ ├── food │ │ ├── create.js │ │ ├── detail.js │ │ ├── explore.js │ │ └── list.js │ ├── home │ │ └── index.js │ ├── topic │ │ ├── detail.js │ │ └── list.js │ └── user │ │ ├── base.js │ │ ├── food_list.js │ │ ├── index.js │ │ ├── list.js │ │ ├── login.js │ │ ├── profile.js │ │ ├── register.js │ │ ├── settings.js │ │ ├── share.js │ │ └── topic_collection.js └── templates │ ├── food │ ├── create.html │ ├── detail.html │ ├── explore.html │ └── list.html │ ├── home │ └── index.html │ ├── topic │ ├── detail.html │ └── list.html │ └── user │ ├── base.html │ ├── food_list.html │ ├── index.html │ ├── list.html │ ├── login.html │ ├── profile.html │ ├── register.html │ ├── settings.html │ ├── share.html │ └── topic_collection.html ├── ext └── __init__.py ├── fixtures └── initial_data.json ├── food ├── __init__.py ├── admin.py ├── apps.py ├── forms.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20160514_0738.py │ ├── 0003_auto_20160515_0738.py │ ├── 0004_auto_20160522_1229.py │ ├── 0005_auto_20160522_1353.py │ ├── 0006_auto_20160528_0511.py │ ├── 0007_auto_20160604_0849.py │ └── __init__.py ├── models.py ├── templates │ └── food │ │ ├── create.html │ │ ├── create.tpl │ │ ├── detail.html │ │ ├── detail.tpl │ │ ├── explore.html │ │ ├── explore.tpl │ │ ├── list.html │ │ └── list.tpl ├── tests.py ├── urls.py └── views.py ├── forum ├── __init__.py ├── admin.py ├── apps.py ├── forms.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20161008_1137.py │ └── __init__.py ├── models.py ├── templates │ └── forum │ │ ├── detail.tpl │ │ └── index.tpl ├── tests.py ├── urls.py └── views.py ├── home ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ └── __init__.py ├── models.py ├── templates │ └── home │ │ ├── base.tpl │ │ ├── index.html │ │ ├── index.tpl │ │ └── navbar.tpl ├── tests.py ├── urls.py └── views.py ├── location ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_place.py │ └── __init__.py ├── models.py ├── tests.py └── views.py ├── manage.py ├── media ├── cache │ ├── 20 │ │ └── 30 │ │ │ └── 203029d21c730d8b90087d5f7b586420.jpg │ ├── 26 │ │ └── e5 │ │ │ └── 26e58558b4f30fd3608eea3b625255a2.jpg │ ├── 38 │ │ └── 57 │ │ │ └── 3857eb185e17c4c4c80b10d1c573cd6f.jpg │ ├── 57 │ │ └── ea │ │ │ └── 57ea8394398cb375b9eb39181c1c2fb9.jpg │ ├── 58 │ │ └── d9 │ │ │ └── 58d95a5917d42a0a7e85fb6b007e93ee.jpg │ ├── 69 │ │ └── c8 │ │ │ └── 69c834a00be1a190b360d2e1a5104c81.jpg │ ├── 70 │ │ └── 08 │ │ │ └── 700890c160c23fbe55b81af05cdff76d.jpg │ ├── 74 │ │ └── b0 │ │ │ └── 74b098dd53b0b05ca6faac60cb3dcbb6.jpg │ ├── 0e │ │ ├── 35 │ │ │ └── 0e356957b375c31f151c91eadff99124.jpg │ │ └── 9f │ │ │ └── 0e9fd5ae5087c241754bb70837e94186.jpg │ ├── 2a │ │ └── 95 │ │ │ └── 2a95bc9e92a1bb2abb8465efb383a00c.jpg │ ├── 3d │ │ └── 5e │ │ │ └── 3d5e42e6f394d5ce7c03bde326490b34.jpg │ ├── 4d │ │ └── bf │ │ │ └── 4dbf961fd10ba4b0309ecbae8daa135e.jpg │ ├── 6a │ │ └── 47 │ │ │ └── 6a474beda343a724939a72b5dca05aa0.jpg │ ├── 6d │ │ └── 50 │ │ │ └── 6d50de93c73b2f41f09b409c75e44277.jpg │ ├── 6f │ │ └── 4d │ │ │ └── 6f4d7d3d9c28a4dfa5d58d908c3a4d38.jpg │ ├── 7d │ │ └── 37 │ │ │ └── 7d372859d9202485c95f6782d118148a.jpg │ ├── aa │ │ └── 59 │ │ │ └── aa59a51567a07379fe4fef58513bce9d.jpg │ ├── b0 │ │ └── 17 │ │ │ └── b017c6a13ffcf20d25e119f44543da24.jpg │ ├── ba │ │ └── 9a │ │ │ └── ba9a8347cf2cf76131439915168c7302.jpg │ ├── c5 │ │ └── f8 │ │ │ └── c5f8d4297de18fc4a2b70fe2ab92d39e.jpg │ ├── d6 │ │ └── 9b │ │ │ └── d69b7579b64edd8d840b90a1b03706fe.jpg │ ├── da │ │ └── 6a │ │ │ └── da6a6363612d0a90fd6aa071281f9b60.jpg │ ├── de │ │ └── eb │ │ │ └── deebf2d808d9329fcdde3e21a4f4507a.jpg │ ├── e0 │ │ └── 52 │ │ │ └── e0528d9879ecb13a6757be825f26f306.jpg │ ├── e2 │ │ └── f8 │ │ │ └── e2f80c38dd496f65d41964304e32de63.jpg │ ├── f9 │ │ └── f1 │ │ │ └── f9f1a8adb2a51f18dd1efcc49f17ecb8.jpg │ └── fa │ │ └── cb │ │ └── facb22872d24e58db5ca6c6b69269cdd.jpg ├── foods │ └── cover │ │ └── 2016 │ │ ├── 05 │ │ ├── 22 │ │ │ └── avatar.png │ │ ├── 25 │ │ │ └── food3.jpg │ │ └── 28 │ │ │ └── b219ebc4b74543a941fbf6af1c178a82b80114f4.jpg │ │ └── 06 │ │ └── 05 │ │ ├── 543198769.jpg │ │ ├── 543198769_YloxsaV.jpg │ │ ├── 543198769_r9eurv5.jpg │ │ ├── 543198769_z9W188q.jpg │ │ └── boluobao.jpg ├── topic │ └── cover │ │ └── 2016 │ │ └── 05 │ │ └── 25 │ │ └── topic5.jpg └── users │ └── avatar │ └── 2016 │ ├── 10 │ └── 06 │ │ └── 7101967.jpeg │ └── 06 │ ├── 04 │ └── avatar.png │ └── 07 │ └── avatar.png ├── message ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ └── __init__.py ├── models.py ├── tasks.py ├── tests.py ├── urls.py └── views.py ├── package.json ├── pineapple ├── __init__.py ├── celery.py ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ └── load_fixtures.py ├── settings.py ├── urls.py ├── views.py └── wsgi.py ├── public └── static │ ├── assets │ └── particles.json │ ├── css │ ├── common.css │ ├── fonts │ │ ├── icomoon.eot │ │ ├── icomoon.svg │ │ ├── icomoon.ttf │ │ └── icomoon.woff │ ├── food_create.css │ ├── food_detail.css │ ├── food_explore.css │ ├── food_list.css │ ├── forum_detail.css │ ├── forum_list.css │ ├── home_index.css │ ├── topic_detail.css │ ├── topic_list.css │ ├── user_base.css │ ├── user_food_list.css │ ├── user_index.css │ ├── user_list.css │ ├── user_login.css │ ├── user_profile.css │ ├── user_register.css │ ├── user_settings.css │ ├── user_share.css │ ├── user_topic_collection.css │ ├── vendors.css │ └── wangEditor.css │ ├── font │ └── font-awesome-4.5.0 │ │ ├── HELP-US-OUT.txt │ │ ├── css │ │ ├── font-awesome.css │ │ └── font-awesome.min.css │ │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ │ ├── less │ │ ├── animated.less │ │ ├── bordered-pulled.less │ │ ├── core.less │ │ ├── fixed-width.less │ │ ├── font-awesome.less │ │ ├── icons.less │ │ ├── larger.less │ │ ├── list.less │ │ ├── mixins.less │ │ ├── path.less │ │ ├── rotated-flipped.less │ │ ├── stacked.less │ │ └── variables.less │ │ └── scss │ │ ├── _animated.scss │ │ ├── _bordered-pulled.scss │ │ ├── _core.scss │ │ ├── _fixed-width.scss │ │ ├── _icons.scss │ │ ├── _larger.scss │ │ ├── _list.scss │ │ ├── _mixins.scss │ │ ├── _path.scss │ │ ├── _rotated-flipped.scss │ │ ├── _stacked.scss │ │ ├── _variables.scss │ │ └── font-awesome.scss │ ├── images │ ├── anonymous.jpg │ ├── back.png │ ├── back2.jpg │ ├── back3.jpg │ ├── bg.jpg │ ├── detail1.jpg │ ├── details.jpg │ ├── female.png │ ├── food.jpg │ ├── food2.jpg │ ├── food3.jpg │ ├── home-header-bg-563ac1688e.png │ ├── male.png │ ├── topic1.jpg │ ├── topic2.jpg │ ├── topic3.jpg │ ├── topic4.jpg │ ├── topic5.jpg │ ├── topic6.jpg │ ├── topic7.jpg │ └── user.jpg │ └── js │ ├── chat.js │ ├── csrf.js │ ├── food_create.js │ ├── food_detail.js │ ├── food_explore.js │ ├── food_list.js │ ├── home_index.js │ ├── js.cookie.js │ ├── topic_detail.js │ ├── topic_list.js │ ├── user_base.js │ ├── user_food_list.js │ ├── user_index.js │ ├── user_list.js │ ├── user_login.js │ ├── user_profile.js │ ├── user_register.js │ ├── user_settings.js │ ├── user_share.js │ ├── user_topic_collection.js │ ├── vendors.js │ └── wangEditor.js ├── requirements.txt ├── run_celery.sh ├── scripts └── deploy.sh ├── search ├── context_processors.py ├── forms.py ├── index.py ├── templates │ └── search │ │ └── search.tpl ├── test.py ├── urls.py └── views.py ├── topic ├── __init__.py ├── admin.py ├── apps.py ├── food_list.tpl ├── forms.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20160514_0738.py │ ├── 0003_auto_20160515_0738.py │ ├── 0004_foodtopic_total_collects.py │ ├── 0005_foodtopic_cover_image.py │ ├── 0006_foodtopic_description.py │ └── __init__.py ├── models.py ├── templates │ └── topic │ │ ├── detail.html │ │ ├── detail.tpl │ │ ├── list.html │ │ ├── list.tpl │ │ └── list_ajax.tpl ├── tests.py ├── urls.py └── views.py ├── user ├── __init__.py ├── admin.py ├── apps.py ├── auth.py ├── forms.py ├── middlewares.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20160513_0509.py │ ├── 0003_usersetting_background_img.py │ ├── 0004_auto_20160515_0738.py │ ├── 0005_auto_20160604_0849.py │ └── __init__.py ├── models.py ├── tasks.py ├── templates │ └── user │ │ ├── base.html │ │ ├── base.tpl │ │ ├── chat.tpl │ │ ├── confirm.tpl │ │ ├── food_list.html │ │ ├── food_list.tpl │ │ ├── index.html │ │ ├── index.tpl │ │ ├── list.html │ │ ├── login.html │ │ ├── login.tpl │ │ ├── moments.tpl │ │ ├── profile.html │ │ ├── profile.tpl │ │ ├── register.html │ │ ├── register.tpl │ │ ├── settings.html │ │ ├── settings.tpl │ │ ├── share.html │ │ ├── share.tpl │ │ ├── topic_collection.html │ │ ├── topic_collection.tpl │ │ ├── user_list.html │ │ └── user_list.tpl ├── tests.py ├── urls.py └── views.py ├── utils ├── __init__.py ├── decorators.py ├── form.py ├── mixins.py ├── taggit.py ├── templatetag │ └── __init__.py └── tests.py └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | .DS_Store 3 | __pycache__/ 4 | *.pyc 5 | .coverage 6 | /node_modules/ 7 | *.hot-update.js 8 | *.hot-update.json 9 | public/static/js/main.js 10 | media/ 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - "3.4" 5 | 6 | services: 7 | - mysql 8 | 9 | env: 10 | - DJANGO_VERSION=1.9.6 11 | 12 | before_install: 13 | - mysql -e "create database IF NOT EXISTS test;" -uroot -p mysql 14 | 15 | install: 16 | - pip install -r requirements.txt 17 | 18 | before_script: 19 | - mysql -e 'create database pineapple;' 20 | - python3 manage.py makemigrations 21 | - python3 manage.py migrate 22 | 23 | script: 24 | - python3 manage.py test 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pineapple 2 | 3 | 发现好吃的,好玩的🍍 4 | 5 | [演示地址](http://182.254.232.181/) 6 | 7 | ## intro 8 | 9 | pineapple是一个基于吃货分享的社区。 10 | 11 | 功能: 12 | 13 | - 分享有趣的食物 14 | - 浏览和评论美食专题和介绍 15 | - 轻量简约的社区和社交形式 16 | 17 | ## 部署 18 | 19 | 依赖环境 20 | 21 | - mysql 22 | - redis 23 | - python3.x(virtualenv) 24 | 25 | 初始化 26 | 27 | ``` 28 | pip3 install requirements.txt 29 | python3 manager.py makemigrations 30 | python3 manager.py makemigrations thumbnail 31 | python3 manager.py migrate 32 | ``` 33 | 34 | 测试运行 35 | 36 | ``` 37 | python3 manager.py runserver 38 | ``` 39 | 40 | ## demo 41 | 42 | 首页 43 | 44 | ![home](http://ww2.sinaimg.cn/large/006y8lVagw1f8irflhs4oj31kw0uygub.jpg) 45 | 46 | 专题页 47 | 48 | ![topic](http://ww2.sinaimg.cn/large/006y8lVagw1f8irh8bljrj31kw0w9q6y.jpg) 49 | 50 | 专题内容 51 | 52 | ![topic-detail](http://ww2.sinaimg.cn/large/006y8lVagw1f8iri902zsj31kw0w7dqf.jpg) 53 | 54 | 发现页 55 | 56 | ![explore](http://ww3.sinaimg.cn/large/006y8lVagw1f8irhj3lt7j31kw0w6n1a.jpg) 57 | 58 | 列表页 59 | 60 | ![food](http://ww4.sinaimg.cn/large/006y8lVagw1f8irkb4jhpj31kw0w8ae4.jpg) 61 | 62 | 详情页 63 | 64 | ![food-detail](http://ww3.sinaimg.cn/large/006y8lVagw1f8irhtvqnnj31kw0w7tgf.jpg) 65 | 66 | 用户页 67 | 68 | ![user](http://ww2.sinaimg.cn/large/006y8lVagw1f8irit353pj31kw0w6juj.jpg) 69 | 70 | 私信功能 71 | 72 | ![chat](http://ww3.sinaimg.cn/large/006y8lVagw1f8irj3uim9j31kw0w7n0u.jpg) 73 | 74 | 讨论区 75 | 76 | ![forum](http://ww2.sinaimg.cn/large/006y8lVagw1f8irjhetb7j31kw0w577f.jpg) 77 | 78 | 评论页 79 | 80 | ![post-detail](http://ww3.sinaimg.cn/large/006y8lVagw1f8irko9zx6j31kw0w841p.jpg) 81 | 82 | 分享页 83 | 84 | ![share](http://ww4.sinaimg.cn/large/006y8lVagw1f8irjua6e5j31kw0w6adk.jpg) 85 | 86 | 注册页 87 | 88 | ![register](http://ww1.sinaimg.cn/large/006y8lVagw1f8iriigwwmj31kw0w7gom.jpg) -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/__init__.py -------------------------------------------------------------------------------- /actions/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = 'actions.apps.ActionsConfig' -------------------------------------------------------------------------------- /actions/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Action 4 | 5 | # Register your models here. 6 | class ActionAdmin(admin.ModelAdmin): 7 | model = Action 8 | 9 | 10 | admin.site.register(Action, ActionAdmin) 11 | -------------------------------------------------------------------------------- /actions/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ActionsConfig(AppConfig): 5 | name = 'actions' 6 | verbose_name = '活动' 7 | -------------------------------------------------------------------------------- /actions/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-13 05:09 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | initial = True 13 | 14 | dependencies = [ 15 | ('contenttypes', '0002_remove_content_type_name'), 16 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 17 | ] 18 | 19 | operations = [ 20 | migrations.CreateModel( 21 | name='Action', 22 | fields=[ 23 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 24 | ('verb', models.CharField(max_length=255, verbose_name='活动描述')), 25 | ('target_id', models.PositiveIntegerField(blank=True, db_index=True, null=True, verbose_name='活动对象id')), 26 | ('created', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='活动时间')), 27 | ('target_ct', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='target_obj', to='contenttypes.ContentType', verbose_name='活动对象类型')), 28 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='actions', to=settings.AUTH_USER_MODEL, verbose_name='用户')), 29 | ], 30 | options={ 31 | 'verbose_name': '活动', 32 | 'ordering': ('-created',), 33 | 'verbose_name_plural': '活动描述', 34 | }, 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /actions/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/actions/migrations/__init__.py -------------------------------------------------------------------------------- /actions/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.contenttypes.models import ContentType 3 | from django.contrib.contenttypes.fields import GenericForeignKey 4 | from constants import * 5 | 6 | from user.models import User 7 | 8 | 9 | action_classes = { 10 | SHARE: 'fa-share-alt', 11 | LIKE: 'fa-thumbs-up', 12 | COLLECT: 'fa-bookmark', 13 | FOLLOW: 'fa-eye', 14 | COMMENT: 'fa-comment', 15 | WTA: 'fa-cutlery', 16 | ATE: 'fa-hand-peace-o', 17 | POST: 'fa-send' 18 | } 19 | 20 | # Create your models here. 21 | class Action(models.Model): 22 | user = models.ForeignKey(User, 23 | related_name='actions', 24 | db_index=True, verbose_name='用户') 25 | verb = models.CharField(max_length=255, verbose_name='活动描述') 26 | target_ct = models.ForeignKey(ContentType, 27 | blank=True, 28 | null=True, 29 | related_name='target_obj', verbose_name='活动对象类型') 30 | target_id = models.PositiveIntegerField(null=True, 31 | blank=True, 32 | db_index=True, verbose_name='活动对象id') 33 | target = GenericForeignKey('target_ct', 'target_id') 34 | created = models.DateTimeField(auto_now_add=True, 35 | db_index=True, verbose_name='活动时间') 36 | 37 | def __str__(self): 38 | return self.user.username + self.verb + self.target_ct.app_label 39 | 40 | def get_action_class(self): 41 | return action_classes.get(self.verb, "") 42 | 43 | class Meta: 44 | ordering = ('-created',) 45 | verbose_name = '活动' 46 | verbose_name_plural = '活动描述' -------------------------------------------------------------------------------- /actions/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /actions/utils.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from django.utils import timezone 3 | from django.contrib.contenttypes.models import ContentType 4 | from .models import Action 5 | 6 | 7 | def create_action(user, verb, target=None): 8 | now = timezone.now() 9 | last_minute = now - datetime.timedelta(seconds=120) 10 | similar_actions = Action.objects.filter(user_id=user.id, 11 | verb=verb, 12 | created__gte=last_minute) 13 | if target: 14 | target_ct = ContentType.objects.get_for_model(target) 15 | similar_actions = similar_actions.filter(target_ct=target_ct, 16 | target_id=target.id) 17 | 18 | if not similar_actions: 19 | action = Action(user=user, verb=verb, target=target) 20 | action.save() 21 | return True 22 | return False 23 | -------------------------------------------------------------------------------- /actions/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/api/__init__.py -------------------------------------------------------------------------------- /api/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ApiConfig(AppConfig): 5 | name = 'api' 6 | -------------------------------------------------------------------------------- /api/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/api/migrations/__init__.py -------------------------------------------------------------------------------- /api/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /api/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /api/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /blog/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = 'blog.apps.BlogConfig' -------------------------------------------------------------------------------- /blog/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from comments.models import PostComment 4 | from .models import Post 5 | 6 | # Register your models here. 7 | class PostCommentAdmin(admin.TabularInline): 8 | model = PostComment 9 | 10 | 11 | class PostAdmin(admin.ModelAdmin): 12 | model = Post 13 | 14 | inlines = [PostCommentAdmin] 15 | 16 | 17 | admin.site.register(Post, PostAdmin) 18 | -------------------------------------------------------------------------------- /blog/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class BlogConfig(AppConfig): 5 | name = 'blog' 6 | verbose_name = '博客' -------------------------------------------------------------------------------- /blog/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-14 07:38 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | initial = True 13 | 14 | dependencies = [ 15 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name='Post', 21 | fields=[ 22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 23 | ('title', models.CharField(max_length=128, verbose_name='标题')), 24 | ('body', models.TextField(verbose_name='内容')), 25 | ('created', models.DateTimeField(auto_now_add=True, verbose_name='创建日期')), 26 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='posts', to=settings.AUTH_USER_MODEL, verbose_name='用户')), 27 | ], 28 | options={ 29 | 'verbose_name_plural': '博客', 30 | 'verbose_name': '博客', 31 | }, 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /blog/migrations/0002_auto_20160514_0813.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-14 08:13 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('blog', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterModelOptions( 16 | name='post', 17 | options={'verbose_name': '帖子', 'verbose_name_plural': '帖子'}, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /blog/migrations/0003_auto_20161008_1137.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-10-08 03:37 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('blog', '0002_auto_20160514_0813'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterModelOptions( 16 | name='post', 17 | options={'verbose_name': '博客', 'verbose_name_plural': '博客'}, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /blog/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/blog/migrations/__init__.py -------------------------------------------------------------------------------- /blog/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from user.models import User 4 | 5 | # Create your models here. 6 | class Post(models.Model): 7 | title = models.CharField(max_length=128, verbose_name='标题') 8 | user = models.ForeignKey(User, related_name='posts', verbose_name='用户') 9 | body = models.TextField(verbose_name='内容') 10 | created = models.DateTimeField(auto_now_add=True, verbose_name='创建日期') 11 | 12 | class Meta: 13 | verbose_name = '博客' 14 | verbose_name_plural = '博客' 15 | -------------------------------------------------------------------------------- /blog/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /blog/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /comments/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = 'comments.apps.CommentsConfig' -------------------------------------------------------------------------------- /comments/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import FoodComment 4 | 5 | 6 | class FoodCommentAdmin(admin.ModelAdmin): 7 | model = FoodComment 8 | 9 | 10 | admin.site.register(FoodComment) 11 | -------------------------------------------------------------------------------- /comments/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CommentsConfig(AppConfig): 5 | name = 'comments' 6 | verbose_name = '评论' -------------------------------------------------------------------------------- /comments/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | from .models import FoodComment, ForumPostComment 4 | from utils.mixins import CleanContentMixin 5 | 6 | class FoodCommentForm(forms.ModelForm): 7 | 8 | class Meta: 9 | model = FoodComment 10 | fields = ('content',) 11 | 12 | class ForumPostCommentForm(forms.ModelForm, CleanContentMixin): 13 | 14 | class Meta: 15 | model = ForumPostComment 16 | fields = ('content',) -------------------------------------------------------------------------------- /comments/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-13 05:09 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | initial = True 13 | 14 | dependencies = [ 15 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name='Comment', 21 | fields=[ 22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 23 | ('content', models.CharField(max_length=512, verbose_name='评论内容')), 24 | ('created', models.DateTimeField(auto_now_add=True, verbose_name='评论时间')), 25 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='评论用户')), 26 | ], 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /comments/migrations/0002_auto_20160514_0738.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-14 07:38 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('comments', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='FoodComment', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('content', models.CharField(max_length=512, verbose_name='评论内容')), 20 | ('created', models.DateTimeField(auto_now_add=True, verbose_name='评论时间')), 21 | ], 22 | options={ 23 | 'verbose_name_plural': '美食评论', 24 | 'verbose_name': '美食评论', 25 | }, 26 | ), 27 | migrations.CreateModel( 28 | name='PostComment', 29 | fields=[ 30 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 31 | ('content', models.CharField(max_length=512, verbose_name='评论内容')), 32 | ('created', models.DateTimeField(auto_now_add=True, verbose_name='评论时间')), 33 | ], 34 | options={ 35 | 'verbose_name_plural': '帖子评论', 36 | 'verbose_name': '帖子评论', 37 | }, 38 | ), 39 | migrations.CreateModel( 40 | name='TopicComment', 41 | fields=[ 42 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 43 | ('content', models.CharField(max_length=512, verbose_name='评论内容')), 44 | ('created', models.DateTimeField(auto_now_add=True, verbose_name='评论时间')), 45 | ], 46 | options={ 47 | 'verbose_name_plural': '专题评论', 48 | 'verbose_name': '专题评论', 49 | }, 50 | ), 51 | migrations.RemoveField( 52 | model_name='comment', 53 | name='user', 54 | ), 55 | ] 56 | -------------------------------------------------------------------------------- /comments/migrations/0003_auto_20160514_0738.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-14 07:38 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('topic', '0002_auto_20160514_0738'), 14 | ('food', '0002_auto_20160514_0738'), 15 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 16 | ('blog', '0001_initial'), 17 | ('comments', '0002_auto_20160514_0738'), 18 | ] 19 | 20 | operations = [ 21 | migrations.DeleteModel( 22 | name='Comment', 23 | ), 24 | migrations.AddField( 25 | model_name='topiccomment', 26 | name='topic', 27 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='topic.FoodTopic', verbose_name='专题'), 28 | ), 29 | migrations.AddField( 30 | model_name='topiccomment', 31 | name='user', 32 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='评论用户'), 33 | ), 34 | migrations.AddField( 35 | model_name='postcomment', 36 | name='post', 37 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='blog.Post', verbose_name='帖子'), 38 | ), 39 | migrations.AddField( 40 | model_name='postcomment', 41 | name='user', 42 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='评论用户'), 43 | ), 44 | migrations.AddField( 45 | model_name='foodcomment', 46 | name='food', 47 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='food.Food', verbose_name='美食'), 48 | ), 49 | migrations.AddField( 50 | model_name='foodcomment', 51 | name='user', 52 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='评论用户'), 53 | ), 54 | ] 55 | -------------------------------------------------------------------------------- /comments/migrations/0004_auto_20160528_0533.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-28 05:33 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('comments', '0003_auto_20160514_0738'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='foodcomment', 17 | name='content', 18 | field=models.TextField(max_length=512, verbose_name='评论内容'), 19 | ), 20 | migrations.AlterField( 21 | model_name='postcomment', 22 | name='content', 23 | field=models.TextField(max_length=512, verbose_name='评论内容'), 24 | ), 25 | migrations.AlterField( 26 | model_name='topiccomment', 27 | name='content', 28 | field=models.TextField(max_length=512, verbose_name='评论内容'), 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /comments/migrations/0005_auto_20161003_1730.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-10-03 09:30 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('forum', '0001_initial'), 14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 15 | ('comments', '0004_auto_20160528_0533'), 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name='ForumPostComment', 21 | fields=[ 22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 23 | ('content', models.TextField(max_length=512, verbose_name='评论内容')), 24 | ('created', models.DateTimeField(auto_now_add=True, verbose_name='评论时间')), 25 | ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='forum.ForumPost', verbose_name='帖子')), 26 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='评论用户')), 27 | ], 28 | options={ 29 | 'verbose_name': '帖子评论', 30 | 'verbose_name_plural': '帖子评论', 31 | }, 32 | ), 33 | migrations.AlterModelOptions( 34 | name='postcomment', 35 | options={'verbose_name': '博客评论', 'verbose_name_plural': '博客评论'}, 36 | ), 37 | ] 38 | -------------------------------------------------------------------------------- /comments/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/comments/migrations/__init__.py -------------------------------------------------------------------------------- /comments/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from forum.models import ForumPost 4 | from blog.models import Post as BlogPost 5 | from food.models import Food 6 | from topic.models import FoodTopic 7 | from user.models import User 8 | 9 | # Create your models here. 10 | class Comment(models.Model): 11 | user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='评论用户') 12 | content = models.TextField(max_length=512, verbose_name='评论内容') 13 | created = models.DateTimeField(auto_now_add=True, verbose_name='评论时间') 14 | 15 | def __str__(self): 16 | return "{}: {}".format(self.user, self.content) 17 | 18 | class Meta: 19 | abstract = True 20 | verbose_name = '评论' 21 | verbose_name_plural = '评论' 22 | 23 | 24 | class FoodComment(Comment): 25 | food = models.ForeignKey(Food, on_delete=models.CASCADE, related_name='comments', verbose_name='美食') 26 | 27 | class Meta: 28 | verbose_name = '美食评论' 29 | verbose_name_plural = '美食评论' 30 | 31 | 32 | class TopicComment(Comment): 33 | topic = models.ForeignKey(FoodTopic, on_delete=models.CASCADE, related_name='comments', verbose_name='专题') 34 | 35 | class Meta: 36 | verbose_name = '专题评论' 37 | verbose_name_plural = '专题评论' 38 | 39 | 40 | class ForumPostComment(Comment): 41 | post = models.ForeignKey(ForumPost, on_delete=models.CASCADE, related_name='comments', verbose_name='帖子') 42 | 43 | class Meta: 44 | verbose_name = '帖子评论' 45 | verbose_name_plural = '帖子评论' 46 | 47 | 48 | class PostComment(Comment): 49 | post = models.ForeignKey(BlogPost, on_delete=models.CASCADE, related_name='comments', verbose_name='帖子') 50 | 51 | class Meta: 52 | verbose_name = '博客评论' 53 | verbose_name_plural = '博客评论' 54 | -------------------------------------------------------------------------------- /comments/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /comments/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /conf/gunicorn_conf.py: -------------------------------------------------------------------------------- 1 | bind = '0.0.0.0:8000' 2 | backlog = 2048 3 | 4 | workers = 1 5 | worker_class = 'sync' 6 | worker_connections = 1000 7 | timeout = 30 8 | keepalive = 2 9 | 10 | daemon = False 11 | pidfile = None 12 | umask = 0 13 | user = None 14 | group = None 15 | tmp_upload_dir = None 16 | 17 | errorlog = '-' 18 | loglevel = 'info' 19 | accesslog = '-' 20 | 21 | proc_name = None 22 | 23 | def post_fork(server, worker): 24 | server.log.info("Worker spawned (pid: %s)", worker.pid) 25 | 26 | def pre_fork(server, worker): 27 | pass 28 | 29 | def pre_exec(server): 30 | server.log.info("Forked child, re-executing.") 31 | 32 | def when_ready(server): 33 | server.log.info("Server is ready. Spawning workers") 34 | 35 | def worker_int(worker): 36 | worker.log.info("worker received INT or QUIT signal") 37 | 38 | ## get traceback info 39 | import threading, sys, traceback 40 | id2name = dict([(th.ident, th.name) for th in threading.enumerate()]) 41 | code = [] 42 | for threadId, stack in sys._current_frames().items(): 43 | code.append("\n# Thread: %s(%d)" % (id2name.get(threadId,""), 44 | threadId)) 45 | for filename, lineno, name, line in traceback.extract_stack(stack): 46 | code.append('File: "%s", line %d, in %s' % (filename, 47 | lineno, name)) 48 | if line: 49 | code.append(" %s" % (line.strip())) 50 | worker.log.debug("\n".join(code)) 51 | 52 | def worker_abort(worker): 53 | worker.log.info("worker received SIGABRT signal") 54 | -------------------------------------------------------------------------------- /conf/supervisord.conf: -------------------------------------------------------------------------------- 1 | [program:pineapple] 2 | command=venv/bin/gunicorn pineapple.wsgi:application -c conf/gunicorn_conf.py 3 | directory=/code/pineapple 4 | user=root 5 | stdout_logfile=/var/log/supervisor/pineapple.log 6 | stderr_logfile=/var/log/supervisor/pineapple_err.log 7 | auto_start=True 8 | auto_restart=True 9 | 10 | [supervisord] 11 | 12 | [supervisorctl] 13 | serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket 14 | serverurl=http://127.0.0.1:9001 ; use an http:// url to specify an inet socket 15 | -------------------------------------------------------------------------------- /constants/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | COMMENT_SUCCESS = '评论成功' 3 | COMMENT_FAIL = '评论失败' 4 | COMMENT_AFTER_LOGIN = '请登录后评论' 5 | POST_ALREADY_LIKE = '请勿重复推荐' 6 | SETTING_UPDATE_SUCCESS = '设置更新成功' 7 | SETTING_UPDATE_FAIL = '设置更新失败' 8 | PROFILE_UPDATE_SUCCESS = '资料更新成功' 9 | PROFILE_UPDATE_FAIL = '资料更新失败' 10 | MAX_MESSAGE_LENGTH_REACH = '消息长度最大为200' 11 | MAX_CONTACT_COUNT_REACH = '超出最大联系人长度' 12 | INVALID_TIMESTAMP = '非法时间戳' 13 | 14 | SHARE = '分享了' 15 | POST = '发表了' 16 | LIKE = '喜欢了' 17 | WTA = '想吃' 18 | ATE = '吃过' 19 | FOLLOW = '关注了' 20 | COLLECT = '收藏了' 21 | COMMENT = '评论了' 22 | 23 | REDIS_FOOD_VIEWS_KEY = 'food:{}:views' 24 | REDIS_FOOD_RANKING_KEY = 'food_ranking' 25 | REDIS_MESSAGES_KEY = 'message:{}:store' 26 | REDIS_MESSAGES_UNREAD_KEY = 'message:{}:unread' 27 | REDIS_MESSAGE_USERS_KEY = 'message:users' 28 | REDIS_CONTACTS_KEY = 'contacts:{}:store' 29 | REDIS_POSTS_KEY = 'posts:{}:store' 30 | 31 | STATUS_INVALID_ARGUMENTS = '无效参数' 32 | 33 | JSON_SUCCESS = {'status': True} 34 | JSON_SUCCESS_WITH_DATA = lambda data: dict(data, **JSON_SUCCESS) 35 | JSON_FAIL = lambda reason='':{'status':False, 'reason': reason} 36 | 37 | MAX_MESSAGE_LENGTH = 200 38 | MAX_MESSAGES_COUNT = 100 39 | MESSAGES_TIMEOUT = 3600 * 24 * 7 # 私信缓存一周 40 | MAX_CONTACT_COUNT = 20 41 | POSTS_CACHE_TIME = 30 # 30秒更新一次 42 | MAX_HOT_DISPLAY_COUNT = 20 -------------------------------------------------------------------------------- /dev/commonJSFile/form.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | var loginContainer = document.getElementById("form-container"); 3 | var inputs = loginContainer.getElementsByTagName("input") 4 | 5 | for(var idx=0;idx'; 10 | } 11 | reader.readAsDataURL(file.files[0]); 12 | } 13 | else 14 | { 15 | prevDiv.innerHTML = ''; 16 | } 17 | } 18 | 19 | document.getElementById("preview-input").addEventListener("change", preview, false) 20 | }; 21 | -------------------------------------------------------------------------------- /dev/css/topic/list.less: -------------------------------------------------------------------------------- 1 | @baseColor: rgb(64,163,194); 2 | 3 | .topic-container { 4 | margin-top: 80px; 5 | } 6 | 7 | html body .navbar { 8 | background-color: @baseColor !important; 9 | 10 | .cursor { 11 | background-color: white !important; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /dev/css/user/food_list.less: -------------------------------------------------------------------------------- 1 | @baseFontColor: #5d5d5d; 2 | @baseFontLightColor: #828caa; 3 | @baseFontFamily: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; 4 | 5 | .tab-container { 6 | 7 | @media screen and (max-width: 1150px) { 8 | .eat-container { 9 | width: 748px !important; 10 | } 11 | } 12 | 13 | .eat-container { 14 | margin: 30px auto; 15 | width: 1122px; 16 | 17 | .eat-item { 18 | width: 334px; 19 | float: left; 20 | padding: 5px; 21 | margin: 20px; 22 | background-color: white; 23 | box-shadow: 0 0 3px rgb(207, 207, 207); 24 | 25 | .food-image { 26 | float: left; 27 | width: 80px; 28 | height: 80px; 29 | margin-right: 10px; 30 | background-position: center; 31 | background-repeat: no-repeat; 32 | background-size: cover; 33 | } 34 | 35 | .eat-item-main { 36 | float: left; 37 | width: 230px; 38 | 39 | 40 | .food-title { 41 | margin: 0; 42 | margin-bottom: 5px; 43 | -o-text-overflow: ellipsis; 44 | text-overflow: ellipsis; 45 | overflow: hidden; 46 | white-space: nowrap; 47 | color: @baseFontColor; 48 | font-weight: 400; 49 | 50 | a { 51 | color: inherit; 52 | text-decoration: none; 53 | } 54 | } 55 | 56 | .food-desc { 57 | margin: 0; 58 | -o-text-overflow: ellipsis; 59 | text-overflow: ellipsis; 60 | overflow: hidden; 61 | white-space: nowrap; 62 | color: @baseFontLightColor; 63 | } 64 | 65 | .eat-item-icons { 66 | margin-top: 15px; 67 | margin-right: 5px; 68 | float: right; 69 | 70 | .fa, a { 71 | color: @baseFontLightColor; 72 | margin-left: 5px; 73 | vertical-align: bottom; 74 | 75 | .num { 76 | margin-left: 3px; 77 | font-family: @baseFontFamily; 78 | } 79 | } 80 | } 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /dev/css/user/list.less: -------------------------------------------------------------------------------- 1 | @imagesDir: "/public/static/images"; 2 | @baseFontColor: #5d5d5d; 3 | @baseFontLightColor: #828caa; 4 | @baseFontFamily: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; 5 | 6 | a { 7 | color: inherit; 8 | text-decoration: none; 9 | } 10 | 11 | .tab-container { 12 | 13 | @media screen and (max-width: 1335px) { 14 | .user-item-container { 15 | max-width: 1144px !important; 16 | } 17 | } 18 | 19 | @media screen and (max-width: 1160px) { 20 | .user-item-container { 21 | max-width: 970px !important; 22 | } 23 | } 24 | 25 | .user-item-container { 26 | margin: 0 auto; 27 | padding: 20px 50px; 28 | max-width: 1318px; 29 | 30 | .user-item { 31 | float: left; 32 | width: 150px; 33 | background-color: white; 34 | text-align: center; 35 | box-shadow: 0 0 3px rgb(207, 207, 207); 36 | margin: 12px; 37 | 38 | .user-item-portrait { 39 | width: 65px; 40 | height: 65px; 41 | border-radius: 50%; 42 | margin: 30px auto 20px auto; 43 | background-image: url("@{imagesDir}/anonymous.jpg"); 44 | background-repeat: no-repeat; 45 | background-size: cover; 46 | background-position: center; 47 | } 48 | 49 | .user-item-info { 50 | display: block; 51 | color: @baseFontLightColor; 52 | margin-bottom: 5px; 53 | } 54 | 55 | .user-item-follow-btn { 56 | width: 100%; 57 | height: 40px; 58 | margin-top: 20px; 59 | border: none; 60 | border-top: 1px solid rgb(226, 226, 226); 61 | background-color: white; 62 | color: @baseFontColor; 63 | font-family: @baseFontFamily; 64 | font-size: 1.5rem; 65 | outline: none; 66 | cursor: pointer; 67 | } 68 | 69 | .has-follow { 70 | color: @baseFontLightColor !important; 71 | } 72 | 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /dev/css/user/profile.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/dev/css/user/profile.less -------------------------------------------------------------------------------- /dev/css/user/share.less: -------------------------------------------------------------------------------- 1 | @baseFontColor: #5d5d5d; 2 | @baseFontLightColor: #828caa; 3 | @baseColor: rgb(122, 167, 242); 4 | @baseDeepColor: rgb(88, 138, 218); 5 | @imagesDir: "/public/static/images"; 6 | 7 | body { 8 | min-width: 1240px; 9 | } 10 | 11 | .tab-container { 12 | .share-container { 13 | margin: 0 150px; 14 | 15 | .share-container-ul { 16 | .share-container-item { 17 | padding: 10px 0; 18 | list-style-type: none; 19 | border-bottom: 1px solid rgb(231, 231, 231); 20 | 21 | .share-time { 22 | float: right; 23 | color: @baseFontLightColor; 24 | margin-top: 16px; 25 | } 26 | 27 | .item-index { 28 | display: inline-block; 29 | margin-right: 15px; 30 | vertical-align: top; 31 | margin-top: 16px; 32 | font-size: 1.5rem; 33 | font-weight: bold; 34 | color: @baseFontLightColor; 35 | } 36 | 37 | .images-item { 38 | display: inline-block; 39 | width: 70px; 40 | height: 70px; 41 | margin-right: 20px; 42 | background-image: url("@{imagesDir}/topic1.jpg"); 43 | background-position: center; 44 | background-repeat: no-repeat; 45 | background-size: cover; 46 | } 47 | 48 | .item-main { 49 | display: inline-block; 50 | 51 | .item-title { 52 | margin: 16px 0; 53 | color: @baseColor; 54 | font-size: 1.5rem; 55 | font-weight: 400; 56 | 57 | a { 58 | color: inherit; 59 | text-decoration: none; 60 | 61 | &:hover { 62 | color: @baseDeepColor; 63 | } 64 | } 65 | } 66 | 67 | .item-desc { 68 | margin-bottom: 0; 69 | -o-text-overflow: ellipsis; 70 | text-overflow: ellipsis; 71 | overflow: hidden; 72 | white-space: nowrap; 73 | width: 700px; 74 | color: @baseFontLightColor; 75 | font-weight: normal; 76 | } 77 | } 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /dev/css/user/topic_collection.less: -------------------------------------------------------------------------------- 1 | @baseFontColor: #5d5d5d; 2 | @baseFontLightColor: #828caa; 3 | @baseFontLighterColor: rgb(191, 191, 191); 4 | @baseFontFamily: Lato, "PingFang SC", "Microsoft YaHei", sans-serif; 5 | @baseColor: rgb(122, 167, 242); 6 | 7 | @media screen and (max-width: 1150px) { 8 | .topic-container { 9 | width: 840px !important; 10 | } 11 | } 12 | 13 | .tab-container { 14 | .topic-container { 15 | margin: 30px auto; 16 | width: 1120px; 17 | 18 | .topic-item { 19 | margin: 0 20px 20px 20px; 20 | width: 240px; 21 | padding-bottom: 10px; 22 | background-color: white; 23 | box-shadow: 0 0 3px rgb(207, 207, 207); 24 | color: @baseFontLighterColor; 25 | float: left; 26 | 27 | .topic-item-image-container { 28 | width: 220px; 29 | height: 130px; 30 | margin: 10px auto; 31 | overflow: hidden; 32 | 33 | .topic-item-image { 34 | width: 220px; 35 | height: 130px; 36 | background-position: center; 37 | background-repeat: no-repeat; 38 | background-size: cover; 39 | transition: transform .3s ease; 40 | 41 | &:hover { 42 | transform: scale(1.1, 1.1); 43 | } 44 | } 45 | } 46 | 47 | 48 | .collect-time { 49 | float: left; 50 | margin-left: 10px; 51 | } 52 | 53 | .topic-item-title { 54 | font-weight: normal; 55 | -o-text-overflow: ellipsis; 56 | text-overflow: ellipsis; 57 | overflow: hidden; 58 | white-space: nowrap; 59 | color: @baseFontLightColor; 60 | width: 100%; 61 | padding: 0 10px; 62 | margin-top: 0; 63 | margin-bottom: 0; 64 | text-align: center; 65 | 66 | a { 67 | color: inherit; 68 | text-decoration: none; 69 | } 70 | } 71 | 72 | .topic-item-icons { 73 | margin-right: 20px; 74 | text-align: center; 75 | font-size: 1.5rem; 76 | color: @baseFontLightColor; 77 | 78 | .fa { 79 | margin-left: 5px; 80 | .num { 81 | margin-left: 10px; 82 | font-family: @baseFontFamily; 83 | } 84 | } 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /dev/js/food/detail.js: -------------------------------------------------------------------------------- 1 | require("../../css/common.less"); 2 | require("../../css/food/detail.less"); 3 | window.$ajax = require("../../commonJSFile/ajax.js"); 4 | -------------------------------------------------------------------------------- /dev/js/food/explore.js: -------------------------------------------------------------------------------- 1 | require("../../css/common.less"); 2 | require("../../css/food/explore.less"); 3 | window.$ajax = require("../../commonJSFile/ajax.js"); 4 | -------------------------------------------------------------------------------- /dev/js/food/list.js: -------------------------------------------------------------------------------- 1 | require("../../css/common.less"); 2 | require("../../css/food/list.less"); 3 | window.$ajax = require("../../commonJSFile/ajax.js"); 4 | -------------------------------------------------------------------------------- /dev/js/home/index.js: -------------------------------------------------------------------------------- 1 | require("../../css/common.less"); 2 | require("../../css/home/index.less"); 3 | window.$ajax = require("../../commonJSFile/ajax.js"); 4 | // window.url = "https://api.unsplash.com/photos/random"; 5 | // "fa60305aa82e74134cabc7093ef54c8e2c370c47e73152f72371c828daedfcd7" 6 | 7 | (function() { 8 | var container = document.getElementById("container"); 9 | var clientHeight = document.documentElement.clientHeight; 10 | var first = true; 11 | container.style.height = clientHeight + "px"; 12 | 13 | document.addEventListener("scroll", function() { 14 | if(document.body.scrollTop > clientHeight / 2 && first) { 15 | first = false; 16 | document.getElementById("navbar").className += " over-navbar"; 17 | }else if(document.body.scrollTop < clientHeight / 2 - 1 && !first) { 18 | first = true; 19 | document.getElementById("navbar").className = "navbar"; 20 | } 21 | }, false); 22 | })(); 23 | -------------------------------------------------------------------------------- /dev/js/topic/detail.js: -------------------------------------------------------------------------------- 1 | require("../../css/common.less"); 2 | require("../../css/topic/detail.less"); 3 | window.$ajax = require("../../commonJSFile/ajax.js"); 4 | -------------------------------------------------------------------------------- /dev/js/topic/list.js: -------------------------------------------------------------------------------- 1 | require("../../css/common.less"); 2 | require("../../css/home/index.less"); 3 | require("../../css/topic/list.less"); 4 | window.$ajax = require("../../commonJSFile/ajax.js"); 5 | -------------------------------------------------------------------------------- /dev/js/user/base.js: -------------------------------------------------------------------------------- 1 | require("../../css/common.less"); 2 | require("../../css/user/base.less"); 3 | window.$ajax = require("../../commonJSFile/ajax.js"); 4 | -------------------------------------------------------------------------------- /dev/js/user/food_list.js: -------------------------------------------------------------------------------- 1 | require("../../css/common.less"); 2 | require("../../css/user/base.less"); 3 | require("../../css/user/food_list.less"); 4 | window.$ajax = require("../../commonJSFile/ajax.js"); 5 | -------------------------------------------------------------------------------- /dev/js/user/index.js: -------------------------------------------------------------------------------- 1 | require("../../css/common.less"); 2 | require("../../css/user/base.less"); 3 | require("../../css/user/index.less"); 4 | window.$ajax = require("../../commonJSFile/ajax.js"); 5 | -------------------------------------------------------------------------------- /dev/js/user/list.js: -------------------------------------------------------------------------------- 1 | require("../../css/common.less"); 2 | require("../../css/user/base.less"); 3 | require("../../css/user/list.less"); 4 | window.$ajax = require("../../commonJSFile/ajax.js"); 5 | -------------------------------------------------------------------------------- /dev/js/user/login.js: -------------------------------------------------------------------------------- 1 | require("../../css/common.less"); 2 | require("../../css/user/login_register.less"); 3 | var formAnimate = require("../../commonJSFile/form.js"); 4 | formAnimate(); 5 | window.$ajax = require("../../commonJSFile/ajax.js"); 6 | -------------------------------------------------------------------------------- /dev/js/user/profile.js: -------------------------------------------------------------------------------- 1 | require("../../css/common.less"); 2 | require("../../css/user/base.less"); 3 | require("../../css/user/settings.less"); 4 | require("../../commonJSFile/preview.js")(); 5 | window.$ajax = require("../../commonJSFile/ajax.js"); 6 | -------------------------------------------------------------------------------- /dev/js/user/register.js: -------------------------------------------------------------------------------- 1 | require("../../css/common.less"); 2 | require("../../css/user/login_register.less"); 3 | var formAnimate = require("../../commonJSFile/form.js"); 4 | formAnimate(); 5 | window.$ajax = require("../../commonJSFile/ajax.js"); 6 | -------------------------------------------------------------------------------- /dev/js/user/settings.js: -------------------------------------------------------------------------------- 1 | require("../../css/common.less"); 2 | require("../../css/user/base.less"); 3 | require("../../css/user/settings.less"); 4 | require("../../commonJSFile/preview.js")(); 5 | window.$ajax = require("../../commonJSFile/ajax.js"); 6 | -------------------------------------------------------------------------------- /dev/js/user/share.js: -------------------------------------------------------------------------------- 1 | require("../../css/common.less"); 2 | require("../../css/user/base.less"); 3 | require("../../css/user/share.less"); 4 | window.$ajax = require("../../commonJSFile/ajax.js"); 5 | -------------------------------------------------------------------------------- /dev/js/user/topic_collection.js: -------------------------------------------------------------------------------- 1 | require("../../css/common.less"); 2 | require("../../css/user/base.less"); 3 | require("../../css/user/topic_collection.less"); 4 | window.$ajax = require("../../commonJSFile/ajax.js"); 5 | -------------------------------------------------------------------------------- /dev/templates/user/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 登录 6 | 7 | 8 | 30 |
31 |
32 |

登录

33 |

登录,寻找更好的

34 |
35 |
36 |
37 |
38 | 39 | 40 |
41 |
42 | 43 | 44 |
45 |
46 | 47 |
48 |
49 | 加入我们 50 |
51 |
52 |
53 |
54 | 55 | 56 | -------------------------------------------------------------------------------- /ext/__init__.py: -------------------------------------------------------------------------------- 1 | import redis 2 | from django.conf import settings 3 | 4 | redis_db = redis.StrictRedis(host=settings.REDIS_HOST, 5 | port=settings.REDIS_PORT, 6 | db=settings.REDIS_DB, 7 | decode_responses=True) 8 | -------------------------------------------------------------------------------- /food/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = 'food.apps.FoodConfig' -------------------------------------------------------------------------------- /food/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.core.urlresolvers import reverse 3 | from django.utils.html import format_html 4 | 5 | from comments.models import FoodComment 6 | from .models import FoodCategory, Food 7 | 8 | # Register your models here. 9 | class FoodCategoryAdmin(admin.ModelAdmin): 10 | model = FoodCategory 11 | 12 | 13 | class FoodCommentAdmin(admin.TabularInline): 14 | model = FoodComment 15 | 16 | 17 | class FoodAdmin(admin.ModelAdmin): 18 | model = Food 19 | list_display = ('title', 'category', 'created', 'show_firm_url') 20 | 21 | def show_firm_url(self, obj): 22 | return format_html("{url}", url=reverse('food:detail', kwargs={'food_id': obj.id})) 23 | 24 | show_firm_url.short_description = '地址' 25 | 26 | inlines = [FoodCommentAdmin] 27 | 28 | class Meta: 29 | ordering = ('-created',) 30 | 31 | 32 | admin.site.register(FoodCategory, FoodCategoryAdmin) 33 | admin.site.register(Food, FoodAdmin) -------------------------------------------------------------------------------- /food/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class FoodConfig(AppConfig): 5 | name = 'food' 6 | verbose_name = '食品' -------------------------------------------------------------------------------- /food/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | from .models import Food 4 | from taggit.forms import TagWidget 5 | 6 | 7 | class FoodForm(forms.ModelForm): 8 | 9 | def __init__(self, *args, **kwargs): 10 | super(FoodForm, self).__init__(*args, **kwargs) 11 | self.fields['tags'].required = True 12 | 13 | def clean_tags(self): 14 | tags = self.cleaned_data['tags'] 15 | if len(tags) > 5: 16 | raise forms.ValidationError('最多只能输入5个标签') 17 | return tags 18 | 19 | class Meta: 20 | model = Food 21 | fields = ('title', 'description', 'cover_image', 'link', 'category', 'tags') 22 | # widgets = { 23 | # 'tags': TagWidget(), 24 | # } 25 | -------------------------------------------------------------------------------- /food/migrations/0002_auto_20160514_0738.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-14 07:38 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | import taggit.managers 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('food', '0001_initial'), 13 | ] 14 | 15 | operations = [ 16 | migrations.RemoveField( 17 | model_name='food', 18 | name='comments', 19 | ), 20 | migrations.AlterField( 21 | model_name='food', 22 | name='tags', 23 | field=taggit.managers.TaggableManager(blank=True, help_text='多个标签以逗号分隔', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='标签'), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /food/migrations/0003_auto_20160515_0738.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-15 07:38 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 13 | ('food', '0002_auto_20160514_0738'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AlterModelOptions( 18 | name='food', 19 | options={'verbose_name': '食品', 'verbose_name_plural': '食品'}, 20 | ), 21 | migrations.AlterModelOptions( 22 | name='foodcategory', 23 | options={'verbose_name': '食品分类', 'verbose_name_plural': '食品分类'}, 24 | ), 25 | migrations.RemoveField( 26 | model_name='food', 27 | name='users_collect', 28 | ), 29 | migrations.AddField( 30 | model_name='food', 31 | name='users_ate', 32 | field=models.ManyToManyField(blank=True, related_name='foods_ate', to=settings.AUTH_USER_MODEL, verbose_name='吃过的用户'), 33 | ), 34 | migrations.AddField( 35 | model_name='food', 36 | name='users_wta', 37 | field=models.ManyToManyField(blank=True, related_name='foods_wta', to=settings.AUTH_USER_MODEL, verbose_name='想吃的用户'), 38 | ), 39 | migrations.AlterField( 40 | model_name='food', 41 | name='cover_image', 42 | field=models.ImageField(upload_to='foods/cover/%Y/%m/%d', verbose_name='封面图片'), 43 | ), 44 | ] 45 | -------------------------------------------------------------------------------- /food/migrations/0004_auto_20160522_1229.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-22 12:29 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('food', '0003_auto_20160515_0738'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='food', 18 | name='total_dislikes', 19 | field=models.PositiveIntegerField(db_index=True, default=0, verbose_name='不喜欢数'), 20 | ), 21 | migrations.AddField( 22 | model_name='food', 23 | name='total_likes', 24 | field=models.PositiveIntegerField(db_index=True, default=0, verbose_name='喜欢数'), 25 | ), 26 | migrations.AlterField( 27 | model_name='food', 28 | name='category', 29 | field=models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='foods', to='food.FoodCategory', verbose_name='分类'), 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /food/migrations/0005_auto_20160522_1353.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-22 13:53 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('food', '0004_auto_20160522_1229'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RemoveField( 16 | model_name='food', 17 | name='total_dislikes', 18 | ), 19 | migrations.RemoveField( 20 | model_name='food', 21 | name='total_likes', 22 | ), 23 | migrations.RemoveField( 24 | model_name='food', 25 | name='users_dislike', 26 | ), 27 | migrations.RemoveField( 28 | model_name='food', 29 | name='users_like', 30 | ), 31 | migrations.AddField( 32 | model_name='food', 33 | name='rating_dislikes', 34 | field=models.PositiveIntegerField(blank=True, default=0, editable=False), 35 | ), 36 | migrations.AddField( 37 | model_name='food', 38 | name='rating_likes', 39 | field=models.PositiveIntegerField(blank=True, default=0, editable=False), 40 | ), 41 | ] 42 | -------------------------------------------------------------------------------- /food/migrations/0006_auto_20160528_0511.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-28 05:11 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('food', '0005_auto_20160522_1353'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterModelOptions( 16 | name='food', 17 | options={'ordering': ('-created',), 'verbose_name': '食品', 'verbose_name_plural': '食品'}, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /food/migrations/0007_auto_20160604_0849.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-06-04 08:49 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('food', '0006_auto_20160528_0511'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='food', 18 | name='category', 19 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='foods', to='food.FoodCategory', verbose_name='分类'), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /food/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/food/migrations/__init__.py -------------------------------------------------------------------------------- /food/models.py: -------------------------------------------------------------------------------- 1 | from django.core.urlresolvers import reverse 2 | from django.db import models 3 | 4 | from taggit.managers import TaggableManager 5 | from user.models import User 6 | from updown.fields import RatingField 7 | 8 | 9 | # Create your models here. 10 | class FoodCategory(models.Model): 11 | name = models.CharField(max_length=16, verbose_name='分类名称') 12 | 13 | def __str__(self): 14 | return self.name 15 | 16 | def get_absolute_url(self): 17 | return reverse('food:category', kwargs={'category': self.name}) 18 | 19 | class Meta: 20 | verbose_name = '食品分类' 21 | verbose_name_plural = '食品分类' 22 | 23 | 24 | class Food(models.Model): 25 | title = models.CharField(max_length=128, verbose_name='标题') 26 | description = models.TextField(verbose_name='描述') 27 | cover_image = models.ImageField(upload_to='foods/cover/%Y/%m/%d', verbose_name='封面图片') 28 | rating = RatingField(can_change_vote=True) 29 | users_wta = models.ManyToManyField(User, related_name='foods_wta', blank=True, verbose_name='想吃的用户') 30 | users_ate = models.ManyToManyField(User, related_name='foods_ate', blank=True, verbose_name='吃过的用户') 31 | link = models.URLField(blank=True, verbose_name='相关链接') 32 | category = models.ForeignKey(FoodCategory, related_name='foods', verbose_name='分类') 33 | user = models.ForeignKey(User, related_name='foods_shared', verbose_name='创建用户') 34 | created = models.DateTimeField(auto_now_add=True, verbose_name='添加日期') 35 | tags = TaggableManager(blank=True, help_text='多个标签以逗号分隔', verbose_name="标签") 36 | 37 | @classmethod 38 | def get_food_categorys(self): 39 | return FoodCategory.objects.all() 40 | 41 | def get_absolute_url(self): 42 | return reverse('food:detail', kwargs={'food_id': self.id}) 43 | 44 | def __str__(self): 45 | return self.title 46 | 47 | class Meta: 48 | verbose_name = '食品' 49 | verbose_name_plural = '食品' 50 | ordering = ('-created', ) 51 | 52 | -------------------------------------------------------------------------------- /food/templates/food/explore.tpl: -------------------------------------------------------------------------------- 1 | {% extends 'home/base.tpl' %} 2 | {% load staticfiles %} 3 | {% block head %} 4 | 发现 5 | 6 | {% endblock head %} 7 | {% block content %} 8 |
9 |
10 | 14 |
15 | 16 | 38 |
39 | {% endblock content %} 40 | {% block js %} 41 | 42 | {% endblock js %} -------------------------------------------------------------------------------- /food/templates/food/list.tpl: -------------------------------------------------------------------------------- 1 | {% extends 'home/base.tpl' %} 2 | {% load staticfiles %} 3 | {% load thumbnail %} 4 | {% block head %} 5 | 好吃 6 | 7 | {% endblock head %} 8 | {% block content %} 9 |
10 |
11 | 17 |
18 | 19 |
20 | {% for food in foods %} 21 |
22 | 23 | {% thumbnail food.cover_image "300x200" crop="center" as im %} 24 |
25 | {% endthumbnail %} 26 |
27 |
28 |

{{ food.title }}

29 |
30 | 31 | {% for tag in food.tags.all %} 32 | {{ tag.name }} 33 | {% endfor %} 34 |
35 |
36 |
37 | {% endfor %} 38 |
39 |
40 | {% endblock content %} 41 | {% block js %} 42 | 43 | {% endblock js %} 44 | -------------------------------------------------------------------------------- /food/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from .views import food_latest, food_detail, food_rate, food_category, food_tag, explore, hot, food_wta, food_ate, food_create 4 | 5 | urlpatterns = [ 6 | url(r'^$', food_latest, name='list'), 7 | url(r'^create/$', food_create, name='create'), 8 | url(r'^(?P[\d]+)/$', food_detail, name='detail'), 9 | url(r'^rate/$', food_rate, name='rate'), 10 | url(r'^food_wta/$', food_wta, name='wta'), 11 | url(r'^food_ate/$', food_ate, name='ate'), 12 | url(r'^category/(?P[\w]+)/$', food_category, name='category'), 13 | url(r'^tag/(?P[\w]+)/$', food_tag, name='tag'), 14 | url(r'^explore/$', explore, name='explore'), 15 | url(r'^explore/hot/$', hot, name='hot'), 16 | ] -------------------------------------------------------------------------------- /forum/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/forum/__init__.py -------------------------------------------------------------------------------- /forum/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | from comments.models import ForumPostComment 5 | from .models import ForumPost, Board 6 | 7 | # Register your models here. 8 | class BoardAdmin(admin.ModelAdmin): 9 | model = Board 10 | 11 | class ForumPostCommentAdmin(admin.TabularInline): 12 | model = ForumPostComment 13 | 14 | 15 | class ForumPostAdmin(admin.ModelAdmin): 16 | model = ForumPost 17 | 18 | inlines = [ForumPostCommentAdmin] 19 | 20 | 21 | admin.site.register(ForumPost, ForumPostAdmin) 22 | admin.site.register(Board, BoardAdmin) 23 | -------------------------------------------------------------------------------- /forum/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ForumConfig(AppConfig): 5 | name = 'forum' 6 | verbose_name = '讨论区' 7 | -------------------------------------------------------------------------------- /forum/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | from .models import ForumPost 4 | from utils.mixins import CleanContentMixin 5 | 6 | 7 | class ForumPostForm(forms.ModelForm, CleanContentMixin): 8 | class Meta: 9 | model = ForumPost 10 | fields = ('title', 'content') -------------------------------------------------------------------------------- /forum/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-10-03 09:30 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | initial = True 13 | 14 | dependencies = [ 15 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name='Board', 21 | fields=[ 22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 23 | ('name', models.CharField(max_length=32, verbose_name='名称')), 24 | ('created', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), 25 | ('creator', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='创建者')), 26 | ], 27 | ), 28 | migrations.CreateModel( 29 | name='ForumPost', 30 | fields=[ 31 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 32 | ('title', models.CharField(max_length=64, verbose_name='帖子标题')), 33 | ('content', models.TextField(max_length=5096, verbose_name='帖子内容')), 34 | ('created', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), 35 | ('updated', models.DateTimeField(auto_now=True, verbose_name='更新时间')), 36 | ('total_likes', models.PositiveIntegerField(default=0, verbose_name='推荐数')), 37 | ('board', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='forum.Board', verbose_name='所属板块')), 38 | ('creator', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='创建者')), 39 | ('users_like', models.ManyToManyField(blank=True, related_name='forumposts_liked', to=settings.AUTH_USER_MODEL, verbose_name='推荐的用户')), 40 | ], 41 | ), 42 | ] 43 | -------------------------------------------------------------------------------- /forum/migrations/0002_auto_20161008_1137.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-10-08 03:37 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('forum', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterModelOptions( 16 | name='board', 17 | options={'verbose_name': '板块', 'verbose_name_plural': '板块'}, 18 | ), 19 | migrations.AlterModelOptions( 20 | name='forumpost', 21 | options={'verbose_name': '帖子', 'verbose_name_plural': '帖子'}, 22 | ), 23 | migrations.AlterField( 24 | model_name='forumpost', 25 | name='total_likes', 26 | field=models.IntegerField(default=0, verbose_name='推荐数'), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /forum/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/forum/migrations/__init__.py -------------------------------------------------------------------------------- /forum/models.py: -------------------------------------------------------------------------------- 1 | from django.core.urlresolvers import reverse 2 | from django.db import models 3 | 4 | from user.models import User 5 | 6 | # Create your models here. 7 | class Board(models.Model): 8 | creator = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='创建者') 9 | name = models.CharField(max_length=32, verbose_name='名称') 10 | created = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') 11 | 12 | class Meta: 13 | verbose_name = '板块' 14 | verbose_name_plural = '板块' 15 | 16 | def __str__(self): 17 | return self.name 18 | 19 | 20 | class ForumPost(models.Model): 21 | title = models.CharField(max_length=64, verbose_name='帖子标题') 22 | content = models.TextField(max_length=5096, verbose_name='帖子内容') 23 | creator = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='创建者') 24 | board = models.ForeignKey(Board, blank=True, null=True, verbose_name='所属板块') 25 | created = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') 26 | updated = models.DateTimeField(auto_now=True, verbose_name='更新时间') 27 | users_like = models.ManyToManyField(User, related_name='forumposts_liked', blank=True, verbose_name='推荐的用户') 28 | total_likes = models.IntegerField(default=0, verbose_name='推荐数') 29 | 30 | class Meta: 31 | verbose_name = '帖子' 32 | verbose_name_plural = '帖子' 33 | 34 | def __str__(self): 35 | return self.title 36 | 37 | def get_absolute_url(self): 38 | return reverse('forum:detail', kwargs={'post_id': self.id}) 39 | -------------------------------------------------------------------------------- /forum/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /forum/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from .views import post_index, delete_post, query_post, update_post, post_detail, post_like 4 | 5 | urlpatterns = [ 6 | url(r'^$', post_index, name='index'), 7 | url(r'^post/(?P[\d]+)/$', post_detail, name='detail'), 8 | url(r'^post/(?P[\d]+)/like/$', post_like, name='like'), 9 | url(r'^delete/(?P[\d]+)/$', delete_post, name='delete'), 10 | url(r'^query/$', query_post, name='query'), 11 | url(r'^update/(?P[\d]+)/$', update_post, name='update'), 12 | ] -------------------------------------------------------------------------------- /home/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/home/__init__.py -------------------------------------------------------------------------------- /home/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /home/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class HomeConfig(AppConfig): 5 | name = 'home' 6 | -------------------------------------------------------------------------------- /home/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/home/migrations/__init__.py -------------------------------------------------------------------------------- /home/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /home/templates/home/base.tpl: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {% block head %} 10 | {% endblock head %} 11 | 12 | 13 | {% include 'home/navbar.tpl' %} 14 | {% block content %} 15 | {% endblock content %} 16 | {% include 'user/chat.tpl' %} 17 | 18 | 19 | 20 | 21 | 22 | {% block js %} 23 | {% endblock js %} 24 | 25 | 26 | -------------------------------------------------------------------------------- /home/templates/home/index.tpl: -------------------------------------------------------------------------------- 1 | {% extends 'home/base.tpl' %} 2 | {% load staticfiles %} 3 | {% load thumbnail %} 4 | {% block head %} 5 | 首页 6 | 7 | {% endblock head %} 8 | {% block content %} 9 |
10 |
11 |
12 |
13 | 一起分享和发现好吃的 14 |
15 |
16 | 19 |
20 |
21 | 22 |
23 |
24 |

精选专题

25 |
26 |
27 | {% for topic in topics %} 28 | 41 | {% endfor %} 42 |
43 |
44 | {% endblock content %} 45 | {% block js %} 46 | 47 | {% endblock js %} 48 | -------------------------------------------------------------------------------- /home/templates/home/navbar.tpl: -------------------------------------------------------------------------------- 1 | {% load widget_tweaks %} 2 | -------------------------------------------------------------------------------- /home/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /home/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from .views import home 4 | 5 | urlpatterns = [ 6 | url(r'', home, name='index'), 7 | ] -------------------------------------------------------------------------------- /home/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.views.decorators.cache import cache_page 3 | 4 | from actions.models import Action 5 | from search.forms import SearchForm 6 | from topic.models import FoodTopic 7 | 8 | # 首页 9 | # @cache_page(60) 10 | def home(request): 11 | form = SearchForm() 12 | topics = FoodTopic.objects.order_by('-total_collects').all()[:4] 13 | return render(request, 'home/index.tpl', { 14 | 'search_form': form, 15 | 'topics': topics 16 | }) 17 | -------------------------------------------------------------------------------- /location/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = 'location.apps.LocationConfig' -------------------------------------------------------------------------------- /location/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Province, City 3 | 4 | 5 | # Register your models here. 6 | class CityAdmin(admin.TabularInline): 7 | model = City 8 | 9 | 10 | class ProvinceAdmin(admin.ModelAdmin): 11 | model = Province 12 | 13 | inlines = [ 14 | CityAdmin, 15 | ] 16 | 17 | 18 | admin.site.register(Province, ProvinceAdmin) -------------------------------------------------------------------------------- /location/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class LocationConfig(AppConfig): 5 | name = 'location' 6 | verbose_name = '位置' -------------------------------------------------------------------------------- /location/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-08 12:55 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='City', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('name', models.CharField(max_length=32, verbose_name='城市名称')), 22 | ], 23 | options={ 24 | 'verbose_name_plural': '城市', 25 | 'verbose_name': '城市', 26 | }, 27 | ), 28 | migrations.CreateModel( 29 | name='Province', 30 | fields=[ 31 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 32 | ('name', models.CharField(max_length=32, verbose_name='省份名称')), 33 | ], 34 | options={ 35 | 'verbose_name_plural': '省份', 36 | 'verbose_name': '省份', 37 | }, 38 | ), 39 | migrations.AddField( 40 | model_name='city', 41 | name='province', 42 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='location.Province', verbose_name='省份'), 43 | ), 44 | ] 45 | -------------------------------------------------------------------------------- /location/migrations/0002_place.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-13 05:09 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('location', '0001_initial'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='Place', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('description', models.CharField(max_length=128, verbose_name='地点名称')), 21 | ('city', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='location.City', verbose_name='所在城市')), 22 | ], 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /location/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/location/migrations/__init__.py -------------------------------------------------------------------------------- /location/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | class Province(models.Model): 5 | name = models.CharField(max_length=32, verbose_name='省份名称') 6 | 7 | def __str__(self): 8 | return self.name 9 | 10 | class Meta: 11 | verbose_name = '省份' 12 | verbose_name_plural = '省份' 13 | 14 | 15 | class City(models.Model): 16 | name = models.CharField(max_length=32, verbose_name='城市名称') 17 | province = models.ForeignKey(Province, verbose_name='省份') 18 | 19 | def __str__(self): 20 | return self.name 21 | 22 | class Meta: 23 | verbose_name = '城市' 24 | verbose_name_plural = '城市' 25 | 26 | 27 | class Place(models.Model): 28 | description = models.CharField(max_length=128, verbose_name='地点名称') 29 | city = models.ForeignKey(City, verbose_name='所在城市') 30 | -------------------------------------------------------------------------------- /location/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /location/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pineapple.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /media/cache/0e/35/0e356957b375c31f151c91eadff99124.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/cache/0e/35/0e356957b375c31f151c91eadff99124.jpg -------------------------------------------------------------------------------- /media/cache/0e/9f/0e9fd5ae5087c241754bb70837e94186.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/cache/0e/9f/0e9fd5ae5087c241754bb70837e94186.jpg -------------------------------------------------------------------------------- /media/cache/20/30/203029d21c730d8b90087d5f7b586420.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/cache/20/30/203029d21c730d8b90087d5f7b586420.jpg -------------------------------------------------------------------------------- /media/cache/26/e5/26e58558b4f30fd3608eea3b625255a2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/cache/26/e5/26e58558b4f30fd3608eea3b625255a2.jpg -------------------------------------------------------------------------------- /media/cache/2a/95/2a95bc9e92a1bb2abb8465efb383a00c.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/cache/2a/95/2a95bc9e92a1bb2abb8465efb383a00c.jpg -------------------------------------------------------------------------------- /media/cache/38/57/3857eb185e17c4c4c80b10d1c573cd6f.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/cache/38/57/3857eb185e17c4c4c80b10d1c573cd6f.jpg -------------------------------------------------------------------------------- /media/cache/3d/5e/3d5e42e6f394d5ce7c03bde326490b34.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/cache/3d/5e/3d5e42e6f394d5ce7c03bde326490b34.jpg -------------------------------------------------------------------------------- /media/cache/4d/bf/4dbf961fd10ba4b0309ecbae8daa135e.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/cache/4d/bf/4dbf961fd10ba4b0309ecbae8daa135e.jpg -------------------------------------------------------------------------------- /media/cache/57/ea/57ea8394398cb375b9eb39181c1c2fb9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/cache/57/ea/57ea8394398cb375b9eb39181c1c2fb9.jpg -------------------------------------------------------------------------------- /media/cache/58/d9/58d95a5917d42a0a7e85fb6b007e93ee.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/cache/58/d9/58d95a5917d42a0a7e85fb6b007e93ee.jpg -------------------------------------------------------------------------------- /media/cache/69/c8/69c834a00be1a190b360d2e1a5104c81.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/cache/69/c8/69c834a00be1a190b360d2e1a5104c81.jpg -------------------------------------------------------------------------------- /media/cache/6a/47/6a474beda343a724939a72b5dca05aa0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/cache/6a/47/6a474beda343a724939a72b5dca05aa0.jpg -------------------------------------------------------------------------------- /media/cache/6d/50/6d50de93c73b2f41f09b409c75e44277.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/cache/6d/50/6d50de93c73b2f41f09b409c75e44277.jpg -------------------------------------------------------------------------------- /media/cache/6f/4d/6f4d7d3d9c28a4dfa5d58d908c3a4d38.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/cache/6f/4d/6f4d7d3d9c28a4dfa5d58d908c3a4d38.jpg -------------------------------------------------------------------------------- /media/cache/70/08/700890c160c23fbe55b81af05cdff76d.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/cache/70/08/700890c160c23fbe55b81af05cdff76d.jpg -------------------------------------------------------------------------------- /media/cache/74/b0/74b098dd53b0b05ca6faac60cb3dcbb6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/cache/74/b0/74b098dd53b0b05ca6faac60cb3dcbb6.jpg -------------------------------------------------------------------------------- /media/cache/7d/37/7d372859d9202485c95f6782d118148a.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/cache/7d/37/7d372859d9202485c95f6782d118148a.jpg -------------------------------------------------------------------------------- /media/cache/aa/59/aa59a51567a07379fe4fef58513bce9d.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/cache/aa/59/aa59a51567a07379fe4fef58513bce9d.jpg -------------------------------------------------------------------------------- /media/cache/b0/17/b017c6a13ffcf20d25e119f44543da24.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/cache/b0/17/b017c6a13ffcf20d25e119f44543da24.jpg -------------------------------------------------------------------------------- /media/cache/ba/9a/ba9a8347cf2cf76131439915168c7302.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/cache/ba/9a/ba9a8347cf2cf76131439915168c7302.jpg -------------------------------------------------------------------------------- /media/cache/c5/f8/c5f8d4297de18fc4a2b70fe2ab92d39e.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/cache/c5/f8/c5f8d4297de18fc4a2b70fe2ab92d39e.jpg -------------------------------------------------------------------------------- /media/cache/d6/9b/d69b7579b64edd8d840b90a1b03706fe.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/cache/d6/9b/d69b7579b64edd8d840b90a1b03706fe.jpg -------------------------------------------------------------------------------- /media/cache/da/6a/da6a6363612d0a90fd6aa071281f9b60.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/cache/da/6a/da6a6363612d0a90fd6aa071281f9b60.jpg -------------------------------------------------------------------------------- /media/cache/de/eb/deebf2d808d9329fcdde3e21a4f4507a.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/cache/de/eb/deebf2d808d9329fcdde3e21a4f4507a.jpg -------------------------------------------------------------------------------- /media/cache/e0/52/e0528d9879ecb13a6757be825f26f306.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/cache/e0/52/e0528d9879ecb13a6757be825f26f306.jpg -------------------------------------------------------------------------------- /media/cache/e2/f8/e2f80c38dd496f65d41964304e32de63.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/cache/e2/f8/e2f80c38dd496f65d41964304e32de63.jpg -------------------------------------------------------------------------------- /media/cache/f9/f1/f9f1a8adb2a51f18dd1efcc49f17ecb8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/cache/f9/f1/f9f1a8adb2a51f18dd1efcc49f17ecb8.jpg -------------------------------------------------------------------------------- /media/cache/fa/cb/facb22872d24e58db5ca6c6b69269cdd.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/cache/fa/cb/facb22872d24e58db5ca6c6b69269cdd.jpg -------------------------------------------------------------------------------- /media/foods/cover/2016/05/22/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/foods/cover/2016/05/22/avatar.png -------------------------------------------------------------------------------- /media/foods/cover/2016/05/25/food3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/foods/cover/2016/05/25/food3.jpg -------------------------------------------------------------------------------- /media/foods/cover/2016/05/28/b219ebc4b74543a941fbf6af1c178a82b80114f4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/foods/cover/2016/05/28/b219ebc4b74543a941fbf6af1c178a82b80114f4.jpg -------------------------------------------------------------------------------- /media/foods/cover/2016/06/05/543198769.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/foods/cover/2016/06/05/543198769.jpg -------------------------------------------------------------------------------- /media/foods/cover/2016/06/05/543198769_YloxsaV.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/foods/cover/2016/06/05/543198769_YloxsaV.jpg -------------------------------------------------------------------------------- /media/foods/cover/2016/06/05/543198769_r9eurv5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/foods/cover/2016/06/05/543198769_r9eurv5.jpg -------------------------------------------------------------------------------- /media/foods/cover/2016/06/05/543198769_z9W188q.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/foods/cover/2016/06/05/543198769_z9W188q.jpg -------------------------------------------------------------------------------- /media/foods/cover/2016/06/05/boluobao.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/foods/cover/2016/06/05/boluobao.jpg -------------------------------------------------------------------------------- /media/topic/cover/2016/05/25/topic5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/topic/cover/2016/05/25/topic5.jpg -------------------------------------------------------------------------------- /media/users/avatar/2016/06/04/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/users/avatar/2016/06/04/avatar.png -------------------------------------------------------------------------------- /media/users/avatar/2016/06/07/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/users/avatar/2016/06/07/avatar.png -------------------------------------------------------------------------------- /media/users/avatar/2016/10/06/7101967.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/media/users/avatar/2016/10/06/7101967.jpeg -------------------------------------------------------------------------------- /message/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/message/__init__.py -------------------------------------------------------------------------------- /message/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /message/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MessageConfig(AppConfig): 5 | name = 'message' 6 | -------------------------------------------------------------------------------- /message/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/message/migrations/__init__.py -------------------------------------------------------------------------------- /message/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /message/tasks.py: -------------------------------------------------------------------------------- 1 | from celery import task 2 | from celery.decorators import periodic_task 3 | from constants import * 4 | 5 | from ext import redis_db as rds 6 | from time import time 7 | 8 | import datetime 9 | import json 10 | 11 | @task 12 | def send_msg(sender, receiver, text): 13 | t = time() 14 | msg = json.dumps({ 15 | 'from': sender, 16 | 'to': receiver, 17 | 'msg': text, 18 | 't': t 19 | }) 20 | receiver_key = REDIS_MESSAGES_KEY.format(receiver) 21 | sender_key = REDIS_MESSAGES_KEY.format(sender) 22 | rds.incr(REDIS_MESSAGES_UNREAD_KEY.format(receiver), 1) 23 | rds.lpush(receiver_key, msg) 24 | rds.ltrim(receiver_key, 0, MAX_MESSAGES_COUNT) 25 | rds.lpush(sender_key, msg) 26 | rds.ltrim(sender_key, 0, MAX_MESSAGES_COUNT) 27 | # 添加到最近活动集合 28 | rds.zadd(REDIS_MESSAGE_USERS_KEY, t, receiver) 29 | rds.zadd(REDIS_MESSAGE_USERS_KEY, t, sender) 30 | return True 31 | 32 | 33 | # 清理不活跃的key 34 | @periodic_task(run_every=datetime.timedelta(days=7)) 35 | def clean_inactive_msg(): 36 | now_time = time() 37 | user_ids = rds.zrangebyscore(REDIS_MESSAGE_USERS_KEY, 0, now_time - MESSAGES_TIMEOUT) 38 | for uid in user_ids: 39 | rds.delete(REDIS_MESSAGES_KEY.format(uid)) 40 | rds.delete(REDIS_MESSAGES_UNREAD_KEY.format(uid)) 41 | rds.zremrangebyscore(REDIS_MESSAGE_USERS_KEY, 0, now_time - MESSAGES_TIMEOUT) 42 | -------------------------------------------------------------------------------- /message/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /message/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from .views import send, pull, attention, user_base_profile, pull_new_msgs 4 | 5 | urlpatterns = [ 6 | url(r'^send/$', send, name='send'), 7 | url(r'^pull/$', pull, name='pull'), 8 | url(r'^attention/$', attention, name='attention'), 9 | url(r'^new/$', pull_new_msgs, name='pull_new'), 10 | url(r'^profile/$', user_base_profile, name='profile'), 11 | ] -------------------------------------------------------------------------------- /message/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.contrib.auth.decorators import login_required 3 | from django.db.models import F 4 | from django.http import JsonResponse, HttpResponse 5 | 6 | from constants import * 7 | from ext import redis_db as rds 8 | from user.models import User, UserProfile 9 | from .tasks import send_msg 10 | 11 | import json 12 | 13 | # Create your views here. 14 | @login_required 15 | def send(request): 16 | text = request.POST.get('msg') 17 | receiver = int(request.POST.get('user')) 18 | if len(text) > MAX_MESSAGE_LENGTH: 19 | # 限制消息长度 20 | return JsonResponse(JSON_FAIL(MAX_MESSAGE_LENGTH_REACH)) 21 | sender = request.user.id 22 | send_msg(sender, receiver, text) 23 | # send_msg.delay(sender, receiver, text) # 交给celery完成 24 | return JsonResponse(JSON_SUCCESS) 25 | 26 | @login_required 27 | def pull(request): 28 | receiver = request.user.id 29 | rds.set(REDIS_MESSAGES_UNREAD_KEY.format(receiver), 0) 30 | messages = rds.lrange(REDIS_MESSAGES_KEY.format(receiver), 0, MAX_MESSAGES_COUNT) 31 | messages = [json.loads(msg) for msg in messages] 32 | # return HttpResponse(str(messages), content_type='application/json') 33 | return JsonResponse(JSON_SUCCESS_WITH_DATA({'data': messages}), safe=False) 34 | 35 | @login_required 36 | def attention(request): 37 | receiver = request.user.id 38 | msg_count = rds.get(REDIS_MESSAGES_UNREAD_KEY.format(receiver)) 39 | if not msg_count: 40 | msg_count = 0 41 | return JsonResponse(JSON_SUCCESS_WITH_DATA({'count': int(msg_count)})) 42 | 43 | @login_required 44 | def pull_new_msgs(request): 45 | last_time = float(request.GET.get('time', -1)) 46 | if last_time < 0: 47 | return JsonResponse(JSON_FAIL(INVALID_TIMESTAMP)) 48 | receiver = request.user.id 49 | messages = rds.lrange(REDIS_MESSAGES_KEY.format(receiver), 0, MAX_MESSAGES_COUNT) 50 | new_msgs = [] 51 | for msg in messages: 52 | msg = json.loads(msg) 53 | if float(msg['t']) <= float(last_time): 54 | break 55 | new_msgs.append(msg) 56 | return JsonResponse(JSON_SUCCESS_WITH_DATA({'data': new_msgs}), safe=False) 57 | 58 | 59 | @login_required 60 | def user_base_profile(request): 61 | ids = json.loads(request.POST.get('ids', '[]')) 62 | if len(ids) > MAX_CONTACT_COUNT: 63 | return JsonResponse(JSON_FAIL(MAX_CONTACT_COUNT_REACH)) 64 | if not ids: 65 | profiles = [] 66 | else: 67 | profiles = list(UserProfile.objects.filter(user__in=ids).annotate(name=F('user__username')).values('user', 'name', 'avatar')) 68 | return JsonResponse(JSON_SUCCESS_WITH_DATA({'data': profiles}), safe=False) 69 | 70 | 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pineapple", 3 | "version": "1.0.0", 4 | "description": "get more", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/pineapplers/pineapple.git" 12 | }, 13 | "keywords": [ 14 | "bingolee" 15 | ], 16 | "author": "Rlilyyy", 17 | "license": "ISC", 18 | "bugs": { 19 | "url": "https://github.com/pineapplers/pineapple/issues" 20 | }, 21 | "homepage": "https://github.com/pineapplers/pineapple#readme", 22 | "devDependencies": { 23 | "babel-cli": "^6.9.0", 24 | "babel-core": "^6.8.0", 25 | "babel-loader": "^6.2.4", 26 | "babel-polyfill": "^6.9.1", 27 | "babel-preset-es2015": "^6.9.0", 28 | "css-loader": "^0.23.1", 29 | "extract-text-webpack-plugin": "^1.0.1", 30 | "glob": "^7.0.3", 31 | "html-loader": "^0.4.3", 32 | "html-webpack-plugin": "^2.16.1", 33 | "less": "^2.7.0", 34 | "less-loader": "^2.2.3", 35 | "path": "^0.12.7", 36 | "style-loader": "^0.13.1", 37 | "webpack": "^1.13.0", 38 | "webpack-dev-server": "^1.14.1" 39 | } 40 | } -------------------------------------------------------------------------------- /pineapple/__init__.py: -------------------------------------------------------------------------------- 1 | from .celery import app as celery_app 2 | -------------------------------------------------------------------------------- /pineapple/celery.py: -------------------------------------------------------------------------------- 1 | import os 2 | from celery import Celery 3 | from django.conf import settings 4 | 5 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pineapple.settings') 6 | 7 | app = Celery('pineapple') 8 | 9 | app.config_from_object('django.conf:settings') 10 | app.autodiscover_tasks([]) 11 | -------------------------------------------------------------------------------- /pineapple/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/pineapple/management/__init__.py -------------------------------------------------------------------------------- /pineapple/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/pineapple/management/commands/__init__.py -------------------------------------------------------------------------------- /pineapple/management/commands/load_fixtures.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | 3 | 4 | class Command(BaseCommand): 5 | def handle(self, *args, **options): 6 | from django.core.management import execute_from_command_line 7 | import sys 8 | 9 | execute_from_command_line([sys.argv[0], 'loaddata', 'initial_data']) 10 | -------------------------------------------------------------------------------- /pineapple/urls.py: -------------------------------------------------------------------------------- 1 | """pineapple URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.9/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.conf import settings 17 | from django.conf.urls import url, include, handler404, handler500 18 | from django.conf.urls.static import static 19 | from django.contrib import admin 20 | 21 | from home.views import home 22 | 23 | admin.site.site_header = 'Pineapple' 24 | admin.site.site_title = 'Pineapple Admin' 25 | 26 | handler404 = 'pineapple.views.page_not_found' 27 | handler500 = 'pineapple.views.server_err' 28 | 29 | urlpatterns = [ 30 | url(r'^$', home), 31 | url(r'^admin/', admin.site.urls), 32 | url(r'^home/', include('home.urls', namespace='home')), 33 | url(r'^user/', include('user.urls', namespace='user')), 34 | url(r'^search/', include('search.urls', namespace='search')), 35 | url(r'^topic/', include('topic.urls', namespace='topic')), 36 | url(r'^food/', include('food.urls', namespace='food')), 37 | url(r'^message/', include('message.urls', namespace='message')), 38 | url(r'^forum/', include('forum.urls', namespace='forum')), 39 | ] + static('public/static', document_root=settings.STATIC_ROOT) + static('media/', document_root=settings.MEDIA_ROOT) 40 | 41 | -------------------------------------------------------------------------------- /pineapple/views.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse 2 | 3 | # 404 4 | def page_not_found(request): 5 | return HttpResponse('page not found!') 6 | 7 | # 500 8 | def server_err(request): 9 | return HttpResponse('server error') 10 | -------------------------------------------------------------------------------- /pineapple/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for pineapple project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pineapple.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /public/static/assets/particles.json: -------------------------------------------------------------------------------- 1 | { 2 | "particles": { 3 | "number": { 4 | "value": 80, 5 | "density": { 6 | "enable": true, 7 | "value_area": 800 8 | } 9 | }, 10 | "color": { 11 | "value": "#ECEDEE" 12 | }, 13 | "shape": { 14 | "type": "circle", 15 | "stroke": { 16 | "width": 0.5, 17 | "color": "#000000" 18 | }, 19 | "polygon": { 20 | "nb_sides": 5 21 | }, 22 | "image": { 23 | "src": "img/github.svg", 24 | "width": 100, 25 | "height": 100 26 | } 27 | }, 28 | "opacity": { 29 | "value": 0.8, 30 | "random": false, 31 | "anim": { 32 | "enable": false, 33 | "speed": 1, 34 | "opacity_min": 0.1, 35 | "sync": false 36 | } 37 | }, 38 | "size": { 39 | "value": 8, 40 | "random": true, 41 | "anim": { 42 | "enable": false, 43 | "speed": 1, 44 | "size_min": 0.1, 45 | "sync": false 46 | } 47 | }, 48 | "line_linked": { 49 | "enable": false, 50 | "distance": 300, 51 | "color": "#ffffff", 52 | "opacity": 0.4, 53 | "width": 2 54 | }, 55 | "move": { 56 | "enable": true, 57 | "speed": 12, 58 | "direction": "none", 59 | "random": false, 60 | "straight": false, 61 | "out_mode": "out", 62 | "bounce": false, 63 | "attract": { 64 | "enable": false, 65 | "rotateX": 600, 66 | "rotateY": 1200 67 | } 68 | } 69 | }, 70 | "interactivity": { 71 | "detect_on": "canvas", 72 | "events": { 73 | "onhover": { 74 | "enable": false, 75 | "mode": "repulse" 76 | }, 77 | "onclick": { 78 | "enable": true, 79 | "mode": "push" 80 | }, 81 | "resize": true 82 | }, 83 | "modes": { 84 | "grab": { 85 | "distance": 800, 86 | "line_linked": { 87 | "opacity": 1 88 | } 89 | }, 90 | "bubble": { 91 | "distance": 800, 92 | "size": 80, 93 | "duration": 2, 94 | "opacity": 0.8, 95 | "speed": 1 96 | }, 97 | "repulse": { 98 | "distance": 400, 99 | "duration": 0.4 100 | }, 101 | "push": { 102 | "particles_nb": 4 103 | }, 104 | "remove": { 105 | "particles_nb": 2 106 | } 107 | } 108 | }, 109 | "retina_detect": true 110 | } -------------------------------------------------------------------------------- /public/static/css/fonts/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/public/static/css/fonts/icomoon.eot -------------------------------------------------------------------------------- /public/static/css/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/public/static/css/fonts/icomoon.ttf -------------------------------------------------------------------------------- /public/static/css/fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/public/static/css/fonts/icomoon.woff -------------------------------------------------------------------------------- /public/static/css/food_list.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin-top: 80px; 3 | min-width: 1100px; 4 | background-color: #f1f1f1; 5 | } 6 | .navbar { 7 | background-image: url("/public/static/images/home-header-bg-563ac1688e.png"); 8 | transition: background-color ease-in-out 0.2s; 9 | -o-transition: background-color ease-in-out 0.2s; 10 | -ms-transition: background-color ease-in-out 0.2s; 11 | -moz-transition: background-color ease-in-out 0.2s; 12 | -webkit-transition: background-color ease-in-out 0.2s; 13 | box-shadow: 0 0 4px rgba(0, 0, 0, 0); 14 | } 15 | .list-container { 16 | width: 1020px; 17 | margin: 0 auto; 18 | } 19 | .list-container .list-tab-container { 20 | width: 100%; 21 | padding: 0 15px; 22 | padding-bottom: 35px; 23 | } 24 | .list-container .list-tab-container .tab-ul { 25 | list-style-type: none; 26 | margin: 0; 27 | padding: 0; 28 | } 29 | .list-container .list-tab-container .tab-ul .tab-ul-item { 30 | float: left; 31 | display: block; 32 | width: 90px; 33 | height: 70px; 34 | line-height: 70px; 35 | font-size: 1.6rem; 36 | text-align: center; 37 | color: #909090; 38 | } 39 | .list-container .list-tab-container .tab-ul .current-item { 40 | color: #5d5d5d; 41 | border-bottom: 2px solid #40a3c2; 42 | } 43 | .list-container .list-main-container .item { 44 | float: left; 45 | width: 300px; 46 | margin: 20px; 47 | border-top-left-radius: 5px; 48 | border-top-right-radius: 5px; 49 | background-color: white; 50 | transition: box-shadow ease 0.3s; 51 | -o-transition: box-shadow ease 0.3s; 52 | -ms-transition: box-shadow ease 0.3s; 53 | -moz-transition: box-shadow ease 0.3s; 54 | -webkit-transition: box-shadow ease 0.3s; 55 | overflow: hidden; 56 | } 57 | .list-container .list-main-container .item:hover { 58 | box-shadow: 0 0 8px #939393; 59 | } 60 | .list-container .list-main-container .item a { 61 | text-decoration: none; 62 | color: inherit; 63 | } 64 | .list-container .list-main-container .item .item-img { 65 | height: 200px; 66 | background-position: center; 67 | background-repeat: no-repeat; 68 | background-size: cover; 69 | } 70 | .list-container .list-main-container .item .item-main { 71 | padding-bottom: 20px; 72 | } 73 | .list-container .list-main-container .item .item-main .item-title { 74 | margin: 0; 75 | padding: 12px 0; 76 | text-align: center; 77 | font-weight: normal; 78 | color: #40a3c2; 79 | } 80 | .list-container .list-main-container .item .item-tags { 81 | height: 22px; 82 | overflow: hidden; 83 | padding: 0px 15px; 84 | color: #40a3c2; 85 | font-size: 1.6rem; 86 | } 87 | .list-container .list-main-container .item .item-tags .tag { 88 | display: inline-block; 89 | font-size: 1.4rem; 90 | margin: 0 8px 5px 8px; 91 | } 92 | -------------------------------------------------------------------------------- /public/static/css/forum_list.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin-top: 80px; 3 | min-width: 1100px; 4 | background-color: #f1f1f1; 5 | } 6 | .navbar { 7 | background-image: url("/public/static/images/home-header-bg-563ac1688e.png"); 8 | transition: background-color ease-in-out 0.2s; 9 | -o-transition: background-color ease-in-out 0.2s; 10 | -ms-transition: background-color ease-in-out 0.2s; 11 | -moz-transition: background-color ease-in-out 0.2s; 12 | -webkit-transition: background-color ease-in-out 0.2s; 13 | box-shadow: 0 0 4px rgba(0, 0, 0, 0); 14 | } 15 | .list-container { 16 | width: 1020px; 17 | margin: 0 auto; 18 | } 19 | .post-bar { 20 | background-color: #fff; 21 | min-height: 300px; 22 | max-height: 1000px; 23 | margin-bottom: 20px; 24 | } 25 | .post-title { 26 | outline: none; 27 | width: 100%; 28 | height: 30px; 29 | border-radius: 5px; 30 | border: 1px solid #ccc; 31 | } 32 | .post-content { 33 | width: 100%; 34 | height: 200px; 35 | border-radius: 5px; 36 | border: 1px solid #ccc; 37 | } 38 | .post-submit { 39 | margin: 10px; 40 | float: right; 41 | width: 100px; 42 | height: 30px; 43 | background-color: #40a3c2; 44 | color: #fff; 45 | border: none; 46 | font-size: 16px; 47 | } 48 | table { 49 | width: 100%; 50 | } 51 | .post-board { 52 | margin-left: 30px; 53 | height: 50px; 54 | width: 100%; 55 | line-height: 50px; 56 | font-size: 20px; 57 | color: #778087; 58 | } 59 | .post-search { 60 | border-radius: 5px; 61 | border: 1px solid #ccc; 62 | width: 200px; 63 | height: 30px; 64 | margin-left: 20px; 65 | } 66 | .post-search-btn { 67 | height: 30px; 68 | width: 50px; 69 | background-color: #40a3c2; 70 | color: #fff; 71 | border: none; 72 | margin: auto 10px; 73 | } 74 | .post-order { 75 | text-align: right; 76 | padding: 0 10px 0px; 77 | } 78 | .post-order a { 79 | text-decoration: none; 80 | color: #40a3c2; 81 | } 82 | .post-order .active { 83 | color: #000; 84 | cursor: default; 85 | } 86 | .post { 87 | padding: 10px; 88 | box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.1); 89 | } 90 | .post a { 91 | color: #778087; 92 | text-decoration: none; 93 | cursor: pointer; 94 | } 95 | .rating { 96 | width: 5%; 97 | } 98 | .board { 99 | width: 10%; 100 | } 101 | .title { 102 | font-size: 16px; 103 | width: 75%; 104 | text-overflow : ellipsis; 105 | white-space : nowrap; 106 | overflow : hidden; 107 | } 108 | .creator { 109 | width: 10%; 110 | } 111 | .switch-page { 112 | padding: 5px 5px; 113 | float: right; 114 | text-decoration: none; 115 | color: #40a3c2; 116 | } -------------------------------------------------------------------------------- /public/static/font/font-awesome-4.5.0/HELP-US-OUT.txt: -------------------------------------------------------------------------------- 1 | I hope you love Font Awesome. If you've found it useful, please do me a favor and check out my latest project, 2 | Fonticons (https://fonticons.com). It makes it easy to put the perfect icons on your website. Choose from our awesome, 3 | comprehensive icon sets or copy and paste your own. 4 | 5 | Please. Check it out. 6 | 7 | -Dave Gandy 8 | -------------------------------------------------------------------------------- /public/static/font/font-awesome-4.5.0/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/public/static/font/font-awesome-4.5.0/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /public/static/font/font-awesome-4.5.0/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/public/static/font/font-awesome-4.5.0/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /public/static/font/font-awesome-4.5.0/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/public/static/font/font-awesome-4.5.0/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /public/static/font/font-awesome-4.5.0/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/public/static/font/font-awesome-4.5.0/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /public/static/font/font-awesome-4.5.0/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/public/static/font/font-awesome-4.5.0/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /public/static/font/font-awesome-4.5.0/less/animated.less: -------------------------------------------------------------------------------- 1 | // Animated Icons 2 | // -------------------------- 3 | 4 | .@{fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .@{fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /public/static/font/font-awesome-4.5.0/less/bordered-pulled.less: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em @fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .@{fa-css-prefix}-pull-left { float: left; } 11 | .@{fa-css-prefix}-pull-right { float: right; } 12 | 13 | .@{fa-css-prefix} { 14 | &.@{fa-css-prefix}-pull-left { margin-right: .3em; } 15 | &.@{fa-css-prefix}-pull-right { margin-left: .3em; } 16 | } 17 | 18 | /* Deprecated as of 4.4.0 */ 19 | .pull-right { float: right; } 20 | .pull-left { float: left; } 21 | 22 | .@{fa-css-prefix} { 23 | &.pull-left { margin-right: .3em; } 24 | &.pull-right { margin-left: .3em; } 25 | } 26 | -------------------------------------------------------------------------------- /public/static/font/font-awesome-4.5.0/less/core.less: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /public/static/font/font-awesome-4.5.0/less/fixed-width.less: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .@{fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /public/static/font/font-awesome-4.5.0/less/font-awesome.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables.less"; 7 | @import "mixins.less"; 8 | @import "path.less"; 9 | @import "core.less"; 10 | @import "larger.less"; 11 | @import "fixed-width.less"; 12 | @import "list.less"; 13 | @import "bordered-pulled.less"; 14 | @import "animated.less"; 15 | @import "rotated-flipped.less"; 16 | @import "stacked.less"; 17 | @import "icons.less"; 18 | -------------------------------------------------------------------------------- /public/static/font/font-awesome-4.5.0/less/larger.less: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .@{fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .@{fa-css-prefix}-2x { font-size: 2em; } 11 | .@{fa-css-prefix}-3x { font-size: 3em; } 12 | .@{fa-css-prefix}-4x { font-size: 4em; } 13 | .@{fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /public/static/font/font-awesome-4.5.0/less/list.less: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: @fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .@{fa-css-prefix}-li { 11 | position: absolute; 12 | left: -@fa-li-width; 13 | width: @fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.@{fa-css-prefix}-lg { 17 | left: (-@fa-li-width + (4em / 14)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /public/static/font/font-awesome-4.5.0/less/mixins.less: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | .fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | 14 | .fa-icon-rotate(@degrees, @rotation) { 15 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation); 16 | -webkit-transform: rotate(@degrees); 17 | -ms-transform: rotate(@degrees); 18 | transform: rotate(@degrees); 19 | } 20 | 21 | .fa-icon-flip(@horiz, @vert, @rotation) { 22 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation, mirror=1); 23 | -webkit-transform: scale(@horiz, @vert); 24 | -ms-transform: scale(@horiz, @vert); 25 | transform: scale(@horiz, @vert); 26 | } 27 | -------------------------------------------------------------------------------- /public/static/font/font-awesome-4.5.0/less/path.less: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}'); 7 | src: url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype'), 8 | url('@{fa-font-path}/fontawesome-webfont.woff2?v=@{fa-version}') format('woff2'), 9 | url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'), 10 | url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'), 11 | url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg'); 12 | // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | -------------------------------------------------------------------------------- /public/static/font/font-awesome-4.5.0/less/rotated-flipped.less: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); } 5 | .@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); } 6 | .@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); } 7 | 8 | .@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); } 9 | .@{fa-css-prefix}-flip-vertical { .fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .@{fa-css-prefix}-rotate-90, 15 | :root .@{fa-css-prefix}-rotate-180, 16 | :root .@{fa-css-prefix}-rotate-270, 17 | :root .@{fa-css-prefix}-flip-horizontal, 18 | :root .@{fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /public/static/font/font-awesome-4.5.0/less/stacked.less: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .@{fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .@{fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .@{fa-css-prefix}-inverse { color: @fa-inverse; } 21 | -------------------------------------------------------------------------------- /public/static/font/font-awesome-4.5.0/scss/_animated.scss: -------------------------------------------------------------------------------- 1 | // Spinning Icons 2 | // -------------------------- 3 | 4 | .#{$fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .#{$fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /public/static/font/font-awesome-4.5.0/scss/_bordered-pulled.scss: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em $fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .#{$fa-css-prefix}-pull-left { float: left; } 11 | .#{$fa-css-prefix}-pull-right { float: right; } 12 | 13 | .#{$fa-css-prefix} { 14 | &.#{$fa-css-prefix}-pull-left { margin-right: .3em; } 15 | &.#{$fa-css-prefix}-pull-right { margin-left: .3em; } 16 | } 17 | 18 | /* Deprecated as of 4.4.0 */ 19 | .pull-right { float: right; } 20 | .pull-left { float: left; } 21 | 22 | .#{$fa-css-prefix} { 23 | &.pull-left { margin-right: .3em; } 24 | &.pull-right { margin-left: .3em; } 25 | } 26 | -------------------------------------------------------------------------------- /public/static/font/font-awesome-4.5.0/scss/_core.scss: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /public/static/font/font-awesome-4.5.0/scss/_fixed-width.scss: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .#{$fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /public/static/font/font-awesome-4.5.0/scss/_larger.scss: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .#{$fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .#{$fa-css-prefix}-2x { font-size: 2em; } 11 | .#{$fa-css-prefix}-3x { font-size: 3em; } 12 | .#{$fa-css-prefix}-4x { font-size: 4em; } 13 | .#{$fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /public/static/font/font-awesome-4.5.0/scss/_list.scss: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: $fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .#{$fa-css-prefix}-li { 11 | position: absolute; 12 | left: -$fa-li-width; 13 | width: $fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.#{$fa-css-prefix}-lg { 17 | left: -$fa-li-width + (4em / 14); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /public/static/font/font-awesome-4.5.0/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | @mixin fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | 14 | @mixin fa-icon-rotate($degrees, $rotation) { 15 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}); 16 | -webkit-transform: rotate($degrees); 17 | -ms-transform: rotate($degrees); 18 | transform: rotate($degrees); 19 | } 20 | 21 | @mixin fa-icon-flip($horiz, $vert, $rotation) { 22 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}); 23 | -webkit-transform: scale($horiz, $vert); 24 | -ms-transform: scale($horiz, $vert); 25 | transform: scale($horiz, $vert); 26 | } 27 | -------------------------------------------------------------------------------- /public/static/font/font-awesome-4.5.0/scss/_path.scss: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}'); 7 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'), 8 | url('#{$fa-font-path}/fontawesome-webfont.woff2?v=#{$fa-version}') format('woff2'), 9 | url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'), 10 | url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'), 11 | url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg'); 12 | // src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | -------------------------------------------------------------------------------- /public/static/font/font-awesome-4.5.0/scss/_rotated-flipped.scss: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); } 5 | .#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); } 6 | .#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); } 7 | 8 | .#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); } 9 | .#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .#{$fa-css-prefix}-rotate-90, 15 | :root .#{$fa-css-prefix}-rotate-180, 16 | :root .#{$fa-css-prefix}-rotate-270, 17 | :root .#{$fa-css-prefix}-flip-horizontal, 18 | :root .#{$fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /public/static/font/font-awesome-4.5.0/scss/_stacked.scss: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .#{$fa-css-prefix}-stack-1x, .#{$fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .#{$fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .#{$fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .#{$fa-css-prefix}-inverse { color: $fa-inverse; } 21 | -------------------------------------------------------------------------------- /public/static/font/font-awesome-4.5.0/scss/font-awesome.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables"; 7 | @import "mixins"; 8 | @import "path"; 9 | @import "core"; 10 | @import "larger"; 11 | @import "fixed-width"; 12 | @import "list"; 13 | @import "bordered-pulled"; 14 | @import "animated"; 15 | @import "rotated-flipped"; 16 | @import "stacked"; 17 | @import "icons"; 18 | -------------------------------------------------------------------------------- /public/static/images/anonymous.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/public/static/images/anonymous.jpg -------------------------------------------------------------------------------- /public/static/images/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/public/static/images/back.png -------------------------------------------------------------------------------- /public/static/images/back2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/public/static/images/back2.jpg -------------------------------------------------------------------------------- /public/static/images/back3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/public/static/images/back3.jpg -------------------------------------------------------------------------------- /public/static/images/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/public/static/images/bg.jpg -------------------------------------------------------------------------------- /public/static/images/detail1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/public/static/images/detail1.jpg -------------------------------------------------------------------------------- /public/static/images/details.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/public/static/images/details.jpg -------------------------------------------------------------------------------- /public/static/images/female.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/public/static/images/female.png -------------------------------------------------------------------------------- /public/static/images/food.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/public/static/images/food.jpg -------------------------------------------------------------------------------- /public/static/images/food2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/public/static/images/food2.jpg -------------------------------------------------------------------------------- /public/static/images/food3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/public/static/images/food3.jpg -------------------------------------------------------------------------------- /public/static/images/home-header-bg-563ac1688e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/public/static/images/home-header-bg-563ac1688e.png -------------------------------------------------------------------------------- /public/static/images/male.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/public/static/images/male.png -------------------------------------------------------------------------------- /public/static/images/topic1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/public/static/images/topic1.jpg -------------------------------------------------------------------------------- /public/static/images/topic2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/public/static/images/topic2.jpg -------------------------------------------------------------------------------- /public/static/images/topic3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/public/static/images/topic3.jpg -------------------------------------------------------------------------------- /public/static/images/topic4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/public/static/images/topic4.jpg -------------------------------------------------------------------------------- /public/static/images/topic5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/public/static/images/topic5.jpg -------------------------------------------------------------------------------- /public/static/images/topic6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/public/static/images/topic6.jpg -------------------------------------------------------------------------------- /public/static/images/topic7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/public/static/images/topic7.jpg -------------------------------------------------------------------------------- /public/static/images/user.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/public/static/images/user.jpg -------------------------------------------------------------------------------- /public/static/js/csrf.js: -------------------------------------------------------------------------------- 1 | function getCookie(name) { 2 | var cookieValue = null; 3 | if (document.cookie && document.cookie != '') { 4 | var cookies = document.cookie.split(';'); 5 | for (var i = 0; i < cookies.length; i++) { 6 | var cookie = jQuery.trim(cookies[i]); 7 | if (cookie.substring(0, name.length + 1) == (name + '=')) { 8 | cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); 9 | break; 10 | } 11 | } 12 | } 13 | return cookieValue; 14 | } 15 | 16 | var csrftoken = getCookie('csrftoken'); 17 | 18 | function csrfSafeMethod(method) { 19 | return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); 20 | } 21 | 22 | $.ajaxSetup({ 23 | beforeSend: function(xhr, settings) { 24 | if (!csrfSafeMethod(settings.type) && !this.crossDomain) { 25 | xhr.setRequestHeader("X-CSRFToken", csrftoken); 26 | } 27 | } 28 | }); 29 | -------------------------------------------------------------------------------- /public/static/js/food_detail.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([1],{ 2 | 3 | /***/ 0: 4 | /***/ function(module, exports, __webpack_require__) { 5 | 6 | __webpack_require__(1); 7 | module.exports = __webpack_require__(303); 8 | 9 | 10 | /***/ }, 11 | 12 | /***/ 303: 13 | /***/ function(module, exports, __webpack_require__) { 14 | 15 | "use strict"; 16 | 17 | __webpack_require__(299); 18 | __webpack_require__(304); 19 | window.$ajax = __webpack_require__(302); 20 | 21 | /***/ }, 22 | 23 | /***/ 304: 24 | /***/ function(module, exports) { 25 | 26 | // removed by extract-text-webpack-plugin 27 | 28 | /***/ } 29 | 30 | }); -------------------------------------------------------------------------------- /public/static/js/food_explore.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([2],{ 2 | 3 | /***/ 0: 4 | /***/ function(module, exports, __webpack_require__) { 5 | 6 | __webpack_require__(1); 7 | module.exports = __webpack_require__(305); 8 | 9 | 10 | /***/ }, 11 | 12 | /***/ 305: 13 | /***/ function(module, exports, __webpack_require__) { 14 | 15 | "use strict"; 16 | 17 | __webpack_require__(299); 18 | __webpack_require__(306); 19 | window.$ajax = __webpack_require__(302); 20 | 21 | /***/ }, 22 | 23 | /***/ 306: 24 | /***/ function(module, exports) { 25 | 26 | // removed by extract-text-webpack-plugin 27 | 28 | /***/ } 29 | 30 | }); -------------------------------------------------------------------------------- /public/static/js/food_list.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([3],{ 2 | 3 | /***/ 0: 4 | /***/ function(module, exports, __webpack_require__) { 5 | 6 | __webpack_require__(1); 7 | module.exports = __webpack_require__(307); 8 | 9 | 10 | /***/ }, 11 | 12 | /***/ 307: 13 | /***/ function(module, exports, __webpack_require__) { 14 | 15 | "use strict"; 16 | 17 | __webpack_require__(299); 18 | __webpack_require__(308); 19 | window.$ajax = __webpack_require__(302); 20 | 21 | /***/ }, 22 | 23 | /***/ 308: 24 | /***/ function(module, exports) { 25 | 26 | // removed by extract-text-webpack-plugin 27 | 28 | /***/ } 29 | 30 | }); -------------------------------------------------------------------------------- /public/static/js/home_index.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([4],{ 2 | 3 | /***/ 0: 4 | /***/ function(module, exports, __webpack_require__) { 5 | 6 | __webpack_require__(1); 7 | module.exports = __webpack_require__(309); 8 | 9 | 10 | /***/ }, 11 | 12 | /***/ 309: 13 | /***/ function(module, exports, __webpack_require__) { 14 | 15 | "use strict"; 16 | 17 | __webpack_require__(299); 18 | __webpack_require__(310); 19 | window.$ajax = __webpack_require__(302); 20 | // window.url = "https://api.unsplash.com/photos/random"; 21 | // "fa60305aa82e74134cabc7093ef54c8e2c370c47e73152f72371c828daedfcd7" 22 | 23 | (function () { 24 | var container = document.getElementById("container"); 25 | var clientHeight = document.documentElement.clientHeight; 26 | var first = true; 27 | container.style.height = clientHeight + "px"; 28 | 29 | document.addEventListener("scroll", function () { 30 | if (document.body.scrollTop > clientHeight / 2 && first) { 31 | first = false; 32 | document.getElementById("navbar").className += " over-navbar"; 33 | } else if (document.body.scrollTop < clientHeight / 2 - 1 && !first) { 34 | first = true; 35 | document.getElementById("navbar").className = "navbar"; 36 | } 37 | }, false); 38 | })(); 39 | 40 | /***/ }, 41 | 42 | /***/ 310: 43 | /***/ function(module, exports) { 44 | 45 | // removed by extract-text-webpack-plugin 46 | 47 | /***/ } 48 | 49 | }); -------------------------------------------------------------------------------- /public/static/js/js.cookie.js: -------------------------------------------------------------------------------- 1 | !function(e){var n=!1;if("function"==typeof define&&define.amd&&(define(e),n=!0),"object"==typeof exports&&(module.exports=e(),n=!0),!n){var o=window.Cookies,t=window.Cookies=e();t.noConflict=function(){return window.Cookies=o,t}}}(function(){function e(){for(var e=0,n={};e1){if(i=e({path:"/"},t.defaults,i),"number"==typeof i.expires){var a=new Date;a.setMilliseconds(a.getMilliseconds()+864e5*i.expires),i.expires=a}try{c=JSON.stringify(r),/^[\{\[]/.test(c)&&(r=c)}catch(e){}return r=o.write?o.write(r,n):encodeURIComponent(String(r)).replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g,decodeURIComponent),n=encodeURIComponent(String(n)),n=n.replace(/%(23|24|26|2B|5E|60|7C)/g,decodeURIComponent),n=n.replace(/[\(\)]/g,escape),document.cookie=[n,"=",r,i.expires?"; expires="+i.expires.toUTCString():"",i.path?"; path="+i.path:"",i.domain?"; domain="+i.domain:"",i.secure?"; secure":""].join("")}n||(c={});for(var p=document.cookie?document.cookie.split("; "):[],s=/(%[0-9A-Z]{2})+/g,d=0;d'; 52 | }; 53 | reader.readAsDataURL(file.files[0]); 54 | } else { 55 | prevDiv.innerHTML = ''; 56 | } 57 | } 58 | 59 | document.getElementById("preview-input").addEventListener("change", preview, false); 60 | }; 61 | 62 | /***/ } 63 | 64 | }); -------------------------------------------------------------------------------- /public/static/js/user_register.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([13],{ 2 | 3 | /***/ 0: 4 | /***/ function(module, exports, __webpack_require__) { 5 | 6 | __webpack_require__(1); 7 | module.exports = __webpack_require__(329); 8 | 9 | 10 | /***/ }, 11 | 12 | /***/ 324: 13 | /***/ function(module, exports) { 14 | 15 | // removed by extract-text-webpack-plugin 16 | 17 | /***/ }, 18 | 19 | /***/ 325: 20 | /***/ function(module, exports) { 21 | 22 | "use strict"; 23 | 24 | module.exports = function () { 25 | var loginContainer = document.getElementById("form-container"); 26 | var inputs = loginContainer.getElementsByTagName("input"); 27 | 28 | for (var idx = 0; idx < inputs.length; idx++) { 29 | inputs[idx].addEventListener("focus", function (event) { 30 | var e = window.event || event; 31 | var target = e.srcElement || e.target; 32 | if (target.name === "button") { 33 | return; 34 | } 35 | var label = target.parentNode.firstElementChild; 36 | label.className = "focus-label"; 37 | }, false); 38 | 39 | inputs[idx].addEventListener("blur", function (event) { 40 | var e = window.event || event; 41 | var target = e.srcElement || e.target; 42 | if (target.value === "") { 43 | var label = target.parentNode.firstElementChild; 44 | label.className = ""; 45 | target.className = ""; 46 | } else { 47 | target.className = "compvare-input"; 48 | } 49 | }, false); 50 | } 51 | }; 52 | 53 | /***/ }, 54 | 55 | /***/ 329: 56 | /***/ function(module, exports, __webpack_require__) { 57 | 58 | "use strict"; 59 | 60 | __webpack_require__(299); 61 | __webpack_require__(324); 62 | var formAnimate = __webpack_require__(325); 63 | formAnimate(); 64 | window.$ajax = __webpack_require__(302); 65 | 66 | /***/ } 67 | 68 | }); -------------------------------------------------------------------------------- /public/static/js/user_settings.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([14],{ 2 | 3 | /***/ 0: 4 | /***/ function(module, exports, __webpack_require__) { 5 | 6 | __webpack_require__(1); 7 | module.exports = __webpack_require__(330); 8 | 9 | 10 | /***/ }, 11 | 12 | /***/ 316: 13 | /***/ function(module, exports) { 14 | 15 | // removed by extract-text-webpack-plugin 16 | 17 | /***/ }, 18 | 19 | /***/ 327: 20 | /***/ function(module, exports) { 21 | 22 | // removed by extract-text-webpack-plugin 23 | 24 | /***/ }, 25 | 26 | /***/ 328: 27 | /***/ function(module, exports) { 28 | 29 | 'use strict'; 30 | 31 | module.exports = function () { 32 | function preview(file) { 33 | var prevDiv = document.getElementById('preview-span'); 34 | var file = this; 35 | if (file.files && file.files[0]) { 36 | var reader = new FileReader(); 37 | reader.onload = function (evt) { 38 | prevDiv.innerHTML = ''; 39 | }; 40 | reader.readAsDataURL(file.files[0]); 41 | } else { 42 | prevDiv.innerHTML = ''; 43 | } 44 | } 45 | 46 | document.getElementById("preview-input").addEventListener("change", preview, false); 47 | }; 48 | 49 | /***/ }, 50 | 51 | /***/ 330: 52 | /***/ function(module, exports, __webpack_require__) { 53 | 54 | "use strict"; 55 | 56 | __webpack_require__(299); 57 | __webpack_require__(316); 58 | __webpack_require__(327); 59 | __webpack_require__(328)(); 60 | window.$ajax = __webpack_require__(302); 61 | 62 | /***/ } 63 | 64 | }); -------------------------------------------------------------------------------- /public/static/js/user_share.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([15],{ 2 | 3 | /***/ 0: 4 | /***/ function(module, exports, __webpack_require__) { 5 | 6 | __webpack_require__(1); 7 | module.exports = __webpack_require__(331); 8 | 9 | 10 | /***/ }, 11 | 12 | /***/ 316: 13 | /***/ function(module, exports) { 14 | 15 | // removed by extract-text-webpack-plugin 16 | 17 | /***/ }, 18 | 19 | /***/ 331: 20 | /***/ function(module, exports, __webpack_require__) { 21 | 22 | "use strict"; 23 | 24 | __webpack_require__(299); 25 | __webpack_require__(316); 26 | __webpack_require__(332); 27 | window.$ajax = __webpack_require__(302); 28 | 29 | /***/ }, 30 | 31 | /***/ 332: 32 | /***/ function(module, exports) { 33 | 34 | // removed by extract-text-webpack-plugin 35 | 36 | /***/ } 37 | 38 | }); -------------------------------------------------------------------------------- /public/static/js/user_topic_collection.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([16],{ 2 | 3 | /***/ 0: 4 | /***/ function(module, exports, __webpack_require__) { 5 | 6 | __webpack_require__(1); 7 | module.exports = __webpack_require__(333); 8 | 9 | 10 | /***/ }, 11 | 12 | /***/ 316: 13 | /***/ function(module, exports) { 14 | 15 | // removed by extract-text-webpack-plugin 16 | 17 | /***/ }, 18 | 19 | /***/ 333: 20 | /***/ function(module, exports, __webpack_require__) { 21 | 22 | "use strict"; 23 | 24 | __webpack_require__(299); 25 | __webpack_require__(316); 26 | __webpack_require__(334); 27 | window.$ajax = __webpack_require__(302); 28 | 29 | /***/ }, 30 | 31 | /***/ 334: 32 | /***/ function(module, exports) { 33 | 34 | // removed by extract-text-webpack-plugin 35 | 36 | /***/ } 37 | 38 | }); -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | amqp==1.4.9 2 | anyjson==0.3.3 3 | billiard==3.3.0.23 4 | bleach==1.4.3 5 | celery==3.1.23 6 | Django==1.9.6 7 | django-celery==3.1.17 8 | django-haystack==2.4.1 9 | django-redis==4.4.2 10 | django-taggit==0.18.2 11 | django-updown==1.0.1 12 | django-widget-tweaks==1.4.1 13 | gunicorn==19.5.0 14 | html5lib==0.9999999 15 | kombu==3.0.35 16 | mysqlclient==1.3.7 17 | Pillow==3.3.1 18 | pysolr==3.4.0 19 | pytz==2016.4 20 | redis==2.10.5 21 | requests==2.10.0 22 | six==1.10.0 23 | sorl-thumbnail==12.3 24 | -------------------------------------------------------------------------------- /run_celery.sh: -------------------------------------------------------------------------------- 1 | python3 manage.py celery worker --loglevel=info -------------------------------------------------------------------------------- /scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | cd /code/pineapple 2 | git reset --hard HEAD 3 | git pull -f 4 | if [ -d "venv" ]; then 5 | echo "**> virtualenv exists" 6 | else 7 | echo "**> creating virtualenv" 8 | virtualenv venv 9 | fi 10 | . venv/bin/activate 11 | pip install -r requirements.txt 12 | python manage.py makemigrations 13 | python manage.py migrate 14 | python manage.py test 15 | python manage.py collectstatic 16 | python manage.py load_fixtures 17 | BUILD_ID=dontKillMe nohup gunicorn pineapple.wsgi:application -c conf/gunicorn_conf.py& -------------------------------------------------------------------------------- /search/context_processors.py: -------------------------------------------------------------------------------- 1 | 2 | from .forms import SearchForm 3 | 4 | def search_proc(request): 5 | form = SearchForm() 6 | return { 7 | 'search_form': form 8 | } -------------------------------------------------------------------------------- /search/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | class SearchForm(forms.Form): 4 | q = forms.CharField() 5 | -------------------------------------------------------------------------------- /search/index.py: -------------------------------------------------------------------------------- 1 | from haystack import indexes 2 | 3 | from food.models import Food 4 | 5 | 6 | class FoodIndex(indexes.SearchIndex, indexes.Indexable): 7 | text = indexes.CharField(document=True, use_template=True) 8 | title = indexes.CharField(model_attr='title') 9 | description = indexes.TextField(model_attr='description') 10 | 11 | def get_model(self): 12 | return Food 13 | 14 | def index_queryset(self, using=None): 15 | return self.get_model().objects.all() 16 | -------------------------------------------------------------------------------- /search/templates/search/search.tpl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/search/templates/search/search.tpl -------------------------------------------------------------------------------- /search/test.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /search/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from .views import search 4 | 5 | urlpatterns = [ 6 | url(r'', search, name='search'), 7 | ] -------------------------------------------------------------------------------- /search/views.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.http import HttpResponse, HttpResponseRedirect 3 | from django.shortcuts import render 4 | 5 | from .forms import SearchForm 6 | 7 | from food.models import Food 8 | 9 | from haystack.query import SearchQuerySet 10 | 11 | categorys = Food.get_food_categorys() 12 | 13 | def search(request): 14 | form = SearchForm(request.POST) 15 | if form.is_valid(): 16 | cd = form.cleaned_data 17 | if settings.FULLTEXT_SEARCH is True: 18 | foods = SearchQuerySet().models(Food).filter(content=cd['q']).load_all() 19 | total_results = foods.count() 20 | else: 21 | foods = Food.objects.filter(title__contains=cd['q']) 22 | return render(request, 'food/list.tpl', { 23 | 'foods': foods, 24 | 'categorys': categorys 25 | }) 26 | return HttpResponse(status=400) 27 | 28 | -------------------------------------------------------------------------------- /topic/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = 'topic.apps.TopicConfig' -------------------------------------------------------------------------------- /topic/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.core.urlresolvers import reverse 3 | from django.utils.html import format_html 4 | 5 | from comments.models import TopicComment 6 | from .models import FoodTopic 7 | 8 | # Register your models here. 9 | class TopicCommentAdmin(admin.TabularInline): 10 | model = TopicComment 11 | 12 | 13 | class FoodTopicAdmin(admin.ModelAdmin): 14 | model = FoodTopic 15 | 16 | list_display = ('title', 'show_firm_url') 17 | 18 | def show_firm_url(self, obj): 19 | return format_html("{url}", url=reverse('topic:detail', kwargs={'topic_id': obj.id})) 20 | 21 | show_firm_url.short_description = '地址' 22 | 23 | inlines = [TopicCommentAdmin] 24 | 25 | 26 | admin.site.register(FoodTopic, FoodTopicAdmin) 27 | -------------------------------------------------------------------------------- /topic/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class TopicConfig(AppConfig): 5 | name = 'topic' 6 | verbose_name = '专题' 7 | -------------------------------------------------------------------------------- /topic/food_list.tpl: -------------------------------------------------------------------------------- 1 | {% extends 'user/base.html' %} 2 | {% load staticfiles %} 3 | {% block head %} 4 | 5 | {% endblock head %} 6 | {% block content %} 7 | 8 | {% endblock content %} 9 | {% block js %} 10 | {% endblock js %} -------------------------------------------------------------------------------- /topic/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | from .models import FoodTopic 4 | 5 | class FoodTopicForm(forms.ModelForm): 6 | 7 | class Meta: 8 | model = FoodTopic 9 | 10 | -------------------------------------------------------------------------------- /topic/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-13 05:09 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | ('comments', '0001_initial'), 15 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 16 | ('food', '0001_initial'), 17 | ] 18 | 19 | operations = [ 20 | migrations.CreateModel( 21 | name='FoodTopic', 22 | fields=[ 23 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 24 | ('title', models.CharField(max_length=32, verbose_name='专题名称')), 25 | ('comments', models.ManyToManyField(blank=True, to='comments.Comment', verbose_name='专题评论')), 26 | ('foods', models.ManyToManyField(to='food.Food', verbose_name='美食')), 27 | ('users_collect', models.ManyToManyField(blank=True, related_name='topics_collected', to=settings.AUTH_USER_MODEL, verbose_name='收藏的用户')), 28 | ('users_like', models.ManyToManyField(blank=True, related_name='topics_liked', to=settings.AUTH_USER_MODEL, verbose_name='点赞用户')), 29 | ], 30 | options={ 31 | 'verbose_name': '美食专题', 32 | 'verbose_name_plural': '美食专题', 33 | }, 34 | ), 35 | ] 36 | -------------------------------------------------------------------------------- /topic/migrations/0002_auto_20160514_0738.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-14 07:38 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | import taggit.managers 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('taggit', '0002_auto_20150616_2121'), 13 | ('topic', '0001_initial'), 14 | ] 15 | 16 | operations = [ 17 | migrations.RemoveField( 18 | model_name='foodtopic', 19 | name='comments', 20 | ), 21 | migrations.AddField( 22 | model_name='foodtopic', 23 | name='tags', 24 | field=taggit.managers.TaggableManager(blank=True, help_text='多个标签以逗号分隔', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='标签'), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /topic/migrations/0003_auto_20160515_0738.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-15 07:38 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('topic', '0002_auto_20160514_0738'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RemoveField( 16 | model_name='foodtopic', 17 | name='users_like', 18 | ), 19 | migrations.AlterField( 20 | model_name='foodtopic', 21 | name='foods', 22 | field=models.ManyToManyField(related_name='topics_belong', to='food.Food', verbose_name='美食'), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /topic/migrations/0004_foodtopic_total_collects.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-22 12:30 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('topic', '0003_auto_20160515_0738'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='foodtopic', 17 | name='total_collects', 18 | field=models.PositiveIntegerField(db_index=True, default=0, verbose_name='收藏数'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /topic/migrations/0005_foodtopic_cover_image.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-25 12:54 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('topic', '0004_foodtopic_total_collects'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='foodtopic', 17 | name='cover_image', 18 | field=models.ImageField(default='qwe', upload_to='topic/cover/%Y/%m/%d', verbose_name='封面图片'), 19 | preserve_default=False, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /topic/migrations/0006_foodtopic_description.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-25 12:57 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('topic', '0005_foodtopic_cover_image'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='foodtopic', 17 | name='description', 18 | field=models.TextField(default='123', verbose_name='专题描述'), 19 | preserve_default=False, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /topic/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/topic/migrations/__init__.py -------------------------------------------------------------------------------- /topic/models.py: -------------------------------------------------------------------------------- 1 | from django.core.urlresolvers import reverse 2 | from django.db import models 3 | 4 | from food.models import Food 5 | from taggit.managers import TaggableManager 6 | from user.models import User 7 | 8 | # Create your models here. 9 | class FoodTopic(models.Model): 10 | title = models.CharField(max_length=32, verbose_name='专题名称') 11 | cover_image = models.ImageField(upload_to='topic/cover/%Y/%m/%d', verbose_name='封面图片') 12 | description = models.TextField(verbose_name='专题描述') 13 | users_collect = models.ManyToManyField(User, related_name='topics_collected', blank=True, verbose_name='收藏的用户') 14 | total_collects = models.PositiveIntegerField(db_index=True, default=0, verbose_name='收藏数') 15 | foods = models.ManyToManyField(Food, related_name='topics_belong', verbose_name='美食') 16 | tags = TaggableManager(blank=True, help_text='多个标签以逗号分隔', verbose_name="标签") 17 | 18 | def __str__(self): 19 | return self.title 20 | 21 | def get_absolute_url(self): 22 | return reverse('topic:detail', kwargs={'topic_id': self.id}) 23 | 24 | class Meta: 25 | verbose_name = '美食专题' 26 | verbose_name_plural = '美食专题' 27 | -------------------------------------------------------------------------------- /topic/templates/topic/list.tpl: -------------------------------------------------------------------------------- 1 | {% extends 'home/base.tpl' %} 2 | {% load staticfiles %} 3 | {% load thumbnail %} 4 | {% block head %} 5 | 专题 6 | 7 | {% endblock head %} 8 | {% block content %} 9 |
10 |
11 | {% for topic in topics %} 12 | 28 | {% endfor %} 29 |
30 |
31 | {% endblock content %} 32 | {% block js %} 33 | 34 | {% endblock js %} 35 | -------------------------------------------------------------------------------- /topic/templates/topic/list_ajax.tpl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/topic/templates/topic/list_ajax.tpl -------------------------------------------------------------------------------- /topic/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /topic/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | from .views import topic_list, topic_detail, topic_collect 4 | 5 | urlpatterns = [ 6 | url(r'^$', topic_list, name='list'), 7 | url(r'^(?P[\d]+)/$', topic_detail, name='detail'), 8 | url(r'^collect/$', topic_collect, name='collect'), 9 | ] -------------------------------------------------------------------------------- /topic/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.decorators import login_required 2 | from django.http import JsonResponse 3 | from django.shortcuts import render, get_object_or_404 4 | from django.views.decorators.http import require_POST 5 | 6 | from .models import FoodTopic 7 | 8 | from actions.utils import create_action 9 | from constants import * 10 | from utils import make_paginator 11 | from utils.decorators import ajax_required 12 | 13 | # 专题列表 14 | def topic_list(request): 15 | topics = make_paginator(request, FoodTopic.objects.all()) 16 | if request.is_ajax(): 17 | return render(request, 'topic/list_ajax.tpl', 18 | {'topics': topics}) 19 | return render(request, 'topic/list.tpl', { 20 | 'topics': topics 21 | }) 22 | 23 | # 专题详情 24 | def topic_detail(request, topic_id): 25 | topic = get_object_or_404(FoodTopic, pk=topic_id) 26 | foods = make_paginator(request, topic.foods.all()) 27 | return render(request, 'topic/detail.tpl', { 28 | 'topic': topic, 29 | 'foods': foods 30 | }) 31 | 32 | # 收藏 33 | @login_required 34 | @require_POST 35 | @ajax_required 36 | def topic_collect(request): 37 | topic_id = request.POST.get('id') 38 | action = request.POST.get('action') 39 | if topic_id and action: 40 | topic = FoodTopic.objects.get(pk=topic_id) 41 | if action == 'collect': 42 | topic.users_collect.add(request.user) 43 | topic.total_collects += 1 44 | create_action(request.user, COLLECT, topic) 45 | elif action == 'uncollect': 46 | topic.users_collect.remove(request.user) 47 | topic.total_collects -= 1 48 | else: 49 | return JsonResponse(JSON_FAIL(STATUS_INVALID_ARGUMENTS)) 50 | topic.save() 51 | return JsonResponse(JSON_SUCCESS) 52 | return JsonResponse(JSON_FAIL(STATUS_INVALID_ARGUMENTS), status=400) 53 | -------------------------------------------------------------------------------- /user/__init__.py: -------------------------------------------------------------------------------- 1 | default_app_config = 'user.apps.UserConfig' 2 | -------------------------------------------------------------------------------- /user/admin.py: -------------------------------------------------------------------------------- 1 | from django.core.urlresolvers import reverse 2 | from django.contrib import admin 3 | from django.contrib.admin import AdminSite 4 | from django.contrib.auth.admin import UserAdmin 5 | from django.utils.html import format_html 6 | 7 | from .models import User, UserProfile, UserSetting 8 | 9 | # Register your models here. 10 | class UserProfileAdmin(admin.TabularInline): 11 | model = UserProfile 12 | 13 | 14 | class UserSettingAdmin(admin.TabularInline): 15 | model = UserSetting 16 | 17 | 18 | class UserAdmin(UserAdmin): 19 | fieldsets = ( 20 | ('个人信息', {'fields': ('username', 'email', 'password')}), 21 | ('权限', {'fields': ('is_active', 'is_staff', 'is_superuser')}), 22 | ('登录信息', {'fields': ('date_joined', 'last_login',)}), 23 | ) 24 | list_display = ('username', 'email', 'is_active', 'is_staff', 'is_superuser') 25 | ordering = ('date_joined',) 26 | 27 | inlines = [ 28 | UserProfileAdmin, 29 | UserSettingAdmin 30 | ] 31 | 32 | 33 | admin.site.register(User, UserAdmin) 34 | -------------------------------------------------------------------------------- /user/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UserConfig(AppConfig): 5 | name = 'user' 6 | verbose_name = '用户' 7 | -------------------------------------------------------------------------------- /user/auth.py: -------------------------------------------------------------------------------- 1 | from .models import User 2 | 3 | 4 | class EmailAuthBackend(object): 5 | def authenticate(self, username=None, password=None): 6 | try: 7 | user = User.objects.get(email=username) 8 | if user.check_password(password): 9 | return user 10 | return None 11 | except User.DoesNotExist: 12 | return None 13 | 14 | def get_user(self, user_id): 15 | try: 16 | return User.objects.get(pk=user_id) 17 | except User.DoesNotExist: 18 | return None -------------------------------------------------------------------------------- /user/middlewares.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/user/middlewares.py -------------------------------------------------------------------------------- /user/migrations/0002_auto_20160513_0509.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-13 05:09 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('user', '0001_initial'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AlterField( 18 | model_name='usersetting', 19 | name='user', 20 | field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='settings', to=settings.AUTH_USER_MODEL), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /user/migrations/0003_usersetting_background_img.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-14 08:13 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('user', '0002_auto_20160513_0509'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='usersetting', 17 | name='background_img', 18 | field=models.ImageField(blank=True, upload_to='users/background/%Y/%m/%d', verbose_name='主页背景'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /user/migrations/0004_auto_20160515_0738.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-05-15 07:38 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('user', '0003_usersetting_background_img'), 13 | ] 14 | 15 | operations = [ 16 | migrations.RenameField( 17 | model_name='userprofile', 18 | old_name='sex', 19 | new_name='gender', 20 | ), 21 | migrations.AlterField( 22 | model_name='user', 23 | name='following', 24 | field=models.ManyToManyField(blank=True, related_name='followers', to=settings.AUTH_USER_MODEL), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /user/migrations/0005_auto_20160604_0849.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.9.6 on 2016-06-04 08:49 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('user', '0004_auto_20160515_0738'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='userprofile', 17 | name='introduction', 18 | field=models.TextField(blank=True, max_length=1024, verbose_name='个人简介'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /user/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/user/migrations/__init__.py -------------------------------------------------------------------------------- /user/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import AbstractUser 2 | from django.core.urlresolvers import reverse 3 | from django.db import models 4 | from django.db.models.signals import post_save 5 | from django.dispatch import receiver 6 | 7 | from location.models import City 8 | 9 | # Create your models here. 10 | class User(AbstractUser): 11 | following = models.ManyToManyField('self', blank=True, related_name='followers', symmetrical=False) 12 | 13 | def __str__(self): 14 | return self.username 15 | 16 | def get_absolute_url(self): 17 | return reverse('user:home', kwargs={'user_id': self.id}) 18 | 19 | 20 | class UserSetting(models.Model): 21 | user = models.OneToOneField(User, related_name='settings', on_delete=models.CASCADE) 22 | background_img = models.ImageField(upload_to='users/background/%Y/%m/%d', blank=True, verbose_name='主页背景') 23 | 24 | def __str__(self): 25 | return "{}的设置".format(self.user.username) 26 | 27 | class Meta: 28 | verbose_name = '用户设置' 29 | verbose_name_plural = '用户设置' 30 | 31 | 32 | class UserProfile(models.Model): 33 | user = models.OneToOneField(User, related_name='profile', on_delete=models.CASCADE) 34 | location = models.ForeignKey(City, null=True, blank=True, verbose_name='居住城市') 35 | introduction = models.TextField(max_length=1024, blank=True, verbose_name='个人简介') 36 | gender = models.CharField(max_length=2, choices=[('m', '男'), ('f', '女')], null=True, blank=True, verbose_name='性别') 37 | date_of_birth = models.DateField(blank=True, null=True, verbose_name='出生日期') 38 | avatar = models.ImageField(upload_to='users/avatar/%Y/%m/%d', blank=True, verbose_name='头像') 39 | 40 | def __str__(self): 41 | return '{}的档案'.format(self.user.username) 42 | 43 | class Meta: 44 | verbose_name = '用户档案' 45 | verbose_name_plural = '用户档案' 46 | 47 | 48 | @receiver(post_save, sender=User) 49 | def user_post_save_handler(sender, instance, created, **kwargs): 50 | if created is True and not kwargs.get('raw', False): 51 | UserSetting.objects.create(user=instance) 52 | UserProfile.objects.create(user=instance) 53 | instance.save() 54 | -------------------------------------------------------------------------------- /user/tasks.py: -------------------------------------------------------------------------------- 1 | from celery import task 2 | from django.core import signing 3 | from django.core.urlresolvers import reverse 4 | from django.conf import settings 5 | from django.core.mail import send_mail 6 | from django.template.loader import render_to_string 7 | 8 | @task 9 | def confirm_user(id, username): 10 | token = signing.dumps({'user_id': id}) 11 | url = reverse('user:confirm', kwargs={'token': token}) 12 | subject = '{}, 激活你的帐户'.format(username) 13 | message = render_to_string('user/confirm.tpl', {'url': url}) 14 | mail_sent = send_mail(subject, message, settings.EMAIL_HOST_USER, ['493632323@qq.com']) 15 | return mail_sent 16 | -------------------------------------------------------------------------------- /user/templates/user/chat.tpl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /user/templates/user/confirm.tpl: -------------------------------------------------------------------------------- 1 | 请点击下面的链接确认你的帐户:{{ url }} 2 | -------------------------------------------------------------------------------- /user/templates/user/food_list.tpl: -------------------------------------------------------------------------------- 1 | {% extends 'user/base.tpl' %} 2 | {% load staticfiles %} 3 | {% block head %} 4 | 想吃的 5 | 6 | {% endblock head %} 7 | {% block content %} 8 |
9 |
10 | {% for food in foods %} 11 |
12 |
13 |
14 |

{{ food.title }}

15 |

16 | {{ food.description }} 17 |

18 |
19 | {{ food.rating.likes }} 20 | 21 |
22 |
23 |
24 | {% endfor %} 25 |
26 |
27 | {% endblock content %} 28 | {% block js %} 29 | 30 | {% endblock js %} -------------------------------------------------------------------------------- /user/templates/user/index.tpl: -------------------------------------------------------------------------------- 1 | {% extends 'user/base.tpl' %} 2 | {% load staticfiles %} 3 | {% load humanize %} 4 | {% block head %} 5 | 用户首页 6 | 7 | 13 | {% endblock head %} 14 | {% block content %} 15 |
16 |
17 | {% if actions %} 18 |
19 | {% for action in actions %} 20 |
21 | 22 | {{ action.user }} 23 | {{ action.verb }} 24 | {{ action.target }} 25 | {{ action.created | naturaltime}} 26 |
27 | {% endfor %} 28 |
29 | {% else %} 30 |

ta最近还没有个人动态

31 | {% endif %} 32 |
33 |
34 | {% endblock content %} 35 | {% block js %} 36 | 37 | {% endblock js %} -------------------------------------------------------------------------------- /user/templates/user/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 登录 6 | 7 | 8 | 30 |
31 |
32 |

登录

33 |

登录,寻找更好的

34 |
35 |
36 |
37 |
38 | 39 | 40 |
41 |
42 | 43 | 44 |
45 |
46 | 47 |
48 |
49 | 加入我们 50 |
51 |
52 |
53 |
54 | 55 | 56 | -------------------------------------------------------------------------------- /user/templates/user/login.tpl: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | {% load widget_tweaks %} 3 | 4 | 5 | 6 | 7 | 登录 - Pineapple 8 | 9 | 10 | 23 | 24 | 25 | {% include 'home/navbar.tpl' %} 26 |
27 |
28 |
29 |

登录

30 |

登录,一起发现有趣的事物

31 |
32 |
33 | {% for err, desc in form.errors.items %} 34 | {{ desc }} 35 | {% endfor %} 36 |
37 | {% csrf_token %} 38 |
39 | 40 | {{ form.username | attr:"required:required" }} 41 |
42 |
43 | 44 | {{ form.password | attr:"required:required"}} 45 |
46 |
47 | 48 |
49 | 52 |
53 |
54 |
55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /user/templates/user/moments.tpl: -------------------------------------------------------------------------------- 1 | {% extends 'user/base.tpl' %} 2 | {% load staticfiles %} 3 | {% load humanize %} 4 | {% block head %} 5 | 吃友圈 6 | 7 | 13 | {% endblock head %} 14 | {% block content %} 15 |
16 |
17 | {% if actions %} 18 |
19 | {% for action in actions %} 20 |
21 | 22 | {{ action.user }} 23 | {{ action.verb }} 24 | {{ action.target }} 25 | {{ action.created | naturaltime}} 26 |
27 | {% endfor %} 28 |
29 | {% else %} 30 |

ta最近还没有个人动态

31 | {% endif %} 32 |
33 |
34 | {% endblock content %} 35 | {% block js %} 36 | 37 | {% endblock js %} -------------------------------------------------------------------------------- /user/templates/user/settings.tpl: -------------------------------------------------------------------------------- 1 | {% extends 'user/base.tpl' %} 2 | {% load staticfiles %} 3 | {% block head %} 4 | 5 | {% endblock head %} 6 | {% block content %} 7 |
8 |
9 | 10 |
11 | {% csrf_token %} 12 |
13 | 14 |
15 | 背景预览 16 |
17 | 18 |
19 | 20 |
21 |
22 | 23 |
24 |
25 |
26 |
27 |
28 | {% endblock content %} 29 | {% block js %} 30 | 31 | {% endblock js %} -------------------------------------------------------------------------------- /user/templates/user/share.tpl: -------------------------------------------------------------------------------- 1 | {% extends 'user/base.tpl' %} 2 | {% load staticfiles %} 3 | {% load humanize %} 4 | {% block head %} 5 | 6 | {% endblock head %} 7 | {% block content %} 8 |
9 | 28 |
29 | {% endblock content %} 30 | {% block js %} 31 | 32 | {% endblock js %} 33 | -------------------------------------------------------------------------------- /user/templates/user/topic_collection.tpl: -------------------------------------------------------------------------------- 1 | {% extends 'user/base.tpl' %} 2 | {% load staticfiles %} 3 | {% block head %} 4 | 5 | {% endblock head %} 6 | {% block content %} 7 |
8 |
9 | {% for topic in collections %} 10 |
11 |
12 |
13 |
14 |

15 | {{ topic.title }} 16 |

17 |
18 | {{ topic.total_collects }} 19 |
20 |
21 | {% endfor %} 22 |
23 |
24 | {% endblock content %} 25 | {% block js %} 26 | 27 | {% endblock js %} 28 | -------------------------------------------------------------------------------- /user/templates/user/user_list.tpl: -------------------------------------------------------------------------------- 1 | {% extends 'user/base.tpl' %} 2 | {% load staticfiles %} 3 | {% load thumbnail %} 4 | {% block head %} 5 | 6 | {% endblock head %} 7 | {% block content %} 8 |
9 |
10 | {% if users %} 11 | {% for user in users %} 12 | 23 | {% endfor %} 24 | {% else %} 25 | {% if request.TAB == 'following' %} 26 |

ta还没关注任何人

27 | {% else %} 28 |

ta还没被任何人关注

29 | {% endif %} 30 | {% endif %} 31 |
32 |
33 | {% endblock content %} 34 | {% block js %} 35 | 36 | {% endblock js %} 37 | 38 | -------------------------------------------------------------------------------- /user/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | from django.contrib.auth.views import (password_change, password_change_done, 3 | password_reset, password_reset_done, password_reset_complete, password_reset_confirm) 4 | 5 | from .views import (home, get_user_id, user_login, user_logout, user_register, user_follow, user_followers, user_following, user_posts, 6 | user_confirm, user_settings, user_profile, user_moments, user_shared, user_wants_to_eat, user_ate, topics_collection, get_cities) 7 | 8 | urlpatterns = [ 9 | url(r'^(?P[\d]+)/$', home, name='home'), 10 | url(r'^whoami/$', get_user_id, name='userid'), 11 | 12 | url(r'^login/$', user_login, name='login'), 13 | url(r'^logout/$', user_logout, name='logout'), 14 | url(r'^register/$', user_register, name='register'), 15 | url(r'^confirm/(?P.+)/$', user_confirm, name='confirm'), 16 | 17 | url(r'^password-change/$', password_change, name='password_change'), 18 | url(r'^password-change/done/$', password_change_done, name='password_change_done'), 19 | 20 | url(r'^password-reset/$', password_reset, name='password_reset'), 21 | url(r'^password-reset/done/$', password_reset_done, name='password_reset_done'), 22 | url(r'^password-reset/confirm/(?P[-\w]+)/(?P[-\w]+)/$', password_reset_confirm, name='password_reset_confirm'), 23 | url(r'^password-reset/complete/$', password_reset_complete, name='password_reset_complete'), 24 | 25 | url(r'^settings/$', user_settings, name='settings'), 26 | url(r'^profile/$', user_profile, name='profile'), 27 | url(r'^moments/$', user_moments, name='moments'), 28 | 29 | url(r'^(?P[\d]+)/shared/$', user_shared, name='share'), 30 | url(r'^(?P[\d]+)/following/$', user_following, name='following'), 31 | url(r'^(?P[\d]+)/followers/$', user_followers, name='followers'), 32 | url(r'^(?P[\d]+)/wta/$', user_wants_to_eat, name='wta'), 33 | url(r'^(?P[\d]+)/ate/$', user_ate, name='ate'), 34 | url(r'^(?P[\d]+)/posts/$', user_posts, name='posts'), 35 | url(r'^(?P[\d]+)/topic-collection', topics_collection, name='topic-collection'), 36 | 37 | url(r'^follow/$', user_follow, name='follow'), 38 | url(r'^get_cities/$', get_cities, name='get_cities'), 39 | ] -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | from django.core.paginator import Paginator, InvalidPage, EmptyPage 3 | from django.core.cache import cache 4 | 5 | # Create your views here. 6 | def make_paginator(request, items, per_page=10, cache_key=None, cache_time=0, cahce_pages=10): 7 | """Make paginator.""" 8 | try: 9 | page = int(request.GET.get("page", '1')) 10 | except ValueError: 11 | page = 1 12 | 13 | if cache_key: 14 | _items = cache.get('{}:{}'.format(cache_key, page)) 15 | if _items: 16 | return _items 17 | 18 | paginator = Paginator(items, per_page) 19 | try: 20 | items = paginator.page(page) 21 | except (InvalidPage, EmptyPage): 22 | items = paginator.page(paginator.num_pages) 23 | 24 | if cache_key and page < cahce_pages: 25 | # 缓存分页 26 | cache.set('{}:{}'.format(cache_key, page), items, cache_time) 27 | return items -------------------------------------------------------------------------------- /utils/decorators.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | from django.http import HttpResponseBadRequest 3 | 4 | def ajax_required(view): 5 | @wraps(view) 6 | def wrapper(request, *args, **kws): 7 | if not request.is_ajax(): 8 | return HttpResponseBadRequest() 9 | return view(request, *args, **kws) 10 | return wrapper 11 | 12 | def tab(tab, *, sub_tab=None): 13 | def wrapper(view): 14 | @wraps(view) 15 | def _wrapper(request, *args, **kws): 16 | request.TAB = tab 17 | request.SUBTAB = sub_tab 18 | return view(request, *args, **kws) 19 | return _wrapper 20 | return wrapper -------------------------------------------------------------------------------- /utils/form.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.core.validators import RegexValidator 3 | 4 | def RegexPassWordField(label): 5 | return forms.CharField(label=label, 6 | validators=[RegexValidator('^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d#?!@$%^&*+-]{6,32}$', 7 | message="密码长度不少于6且至少包含一个字母和一个数字")], 8 | widget=forms.PasswordInput) -------------------------------------------------------------------------------- /utils/mixins.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | import bleach 3 | from django.conf import settings 4 | 5 | class CleanContentMixin: 6 | # 防御富文本编辑器造成的XSS注入 7 | def clean_content(self): 8 | content = self.cleaned_data.get('content', '') 9 | cleaned_text = bleach.clean(content, settings.BLEACH_VALID_TAGS, settings.BLEACH_VALID_ATTRS, settings.BLEACH_VALID_STYLES) 10 | return cleaned_text -------------------------------------------------------------------------------- /utils/taggit.py: -------------------------------------------------------------------------------- 1 | 2 | def comma_splitter(tag_string): 3 | tag_string = tag_string.replace(' ', '').replace(',', ',') 4 | return [t.strip().lower() for t in tag_string.split(',') if t.strip()] 5 | -------------------------------------------------------------------------------- /utils/templatetag/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pineapplers/pineapple/dfc8a6772964c96684a9817df9b0970595ea18f2/utils/templatetag/__init__.py -------------------------------------------------------------------------------- /utils/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | --------------------------------------------------------------------------------