diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 9ae827f..0000000 --- a/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -# tools, IDEs, build folders -/.coverage/ -/.eggs/ -/.idea/ -/.tox/ -/build/ -/dist/ -/docs/build/ -/*.egg-info/ - -# Django and Python -*.py[cod] diff --git a/.helm/templates/02-deployment.yaml b/.helm/templates/02-deployment.yaml new file mode 100644 index 0000000..c8b1de5 --- /dev/null +++ b/.helm/templates/02-deployment.yaml @@ -0,0 +1,37 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Chart.Name }} + namespace: {{ .Chart.Name }}-{{ .Values.global.env }} + +spec: + replicas: 1 + selector: + matchLabels: + app: {{ .Chart.Name }} + template: + metadata: + labels: + app: {{ .Chart.Name }} + spec: + containers: + - name: {{ .Chart.Name }} + image: {{ .Values.DockerImage }} + ports: + - containerPort: 8888 + env: + - name: DATABASE_HOST + value: "db-postgresql" + - name: DATABASE_USER + value: "postgres" + - name: DATABASE_PASSWORD + value: {{.Values.DBPwd | required "DBPwd is required" }} + resources: + limits: + cpu: 100m + memory: 150Mi + requests: + cpu: 100m + memory: 150Mi + diff --git a/.helm/templates/03-service.yaml b/.helm/templates/03-service.yaml new file mode 100644 index 0000000..18c0cb1 --- /dev/null +++ b/.helm/templates/03-service.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ .Chart.Name }}-service + namespace: {{ .Chart.Name }}-{{ .Values.global.env }} + +spec: + type: ClusterIP + ports: + - name: http + port: 8888 + targetPort: 8888 + selector: + app: {{ .Chart.Name }} diff --git a/.helm/templates/04-ingress.yaml b/.helm/templates/04-ingress.yaml new file mode 100644 index 0000000..77e28ac --- /dev/null +++ b/.helm/templates/04-ingress.yaml @@ -0,0 +1,16 @@ +apiVersion: networking.k8s.io/v1beta1 +kind: Ingress +metadata: + name: ingress-{{ .Chart.Name }} + namespace: {{ .Chart.Name }}-{{ .Values.global.env }} + + annotations: + kubernetes.io/ingress.class: "nginx" +spec: + rules: + - http: + paths: + - path: / + backend: + serviceName: {{ .Chart.Name }}-service + servicePort: 8888 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0e6a36b..0000000 --- a/.travis.yml +++ /dev/null @@ -1,5 +0,0 @@ -language: python -install: - - pip install virtualenv -script: - - python setup.py test diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..adc4433 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3.6-alpine + +WORKDIR /app + +RUN apk add git gcc musl-dev postgresql-dev && \ + pip install pipenv && \ + git clone https://github.com/shacker/gtd.git /app && \ + pipenv --python 3.6 && \ + pipenv install --dev + +ADD local.py project/. +ADD entrypoint.sh . +RUN chmod +x entrypoint.sh + +CMD [ "./entrypoint.sh" ] diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 68eef42..0000000 --- a/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2010, Scot Hacker, Birdhouse Arts and individual contributors. -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of Birdhouse Arts nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index a13f7f3..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,4 +0,0 @@ -include LICENSE -include README.rst -recursive-include todo/static * -recursive-include todo/templates * diff --git a/README.md b/README.md new file mode 100644 index 0000000..4d85b72 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# [WiP] PoC of DevOps magic + +### Pre requires: +1. Any x86_64 Linux distribution. Tested on Debian 10, but must work on any modern Linux system +2. Internet access +3. wget +4. bash/dash shell +5. GNU awk +5. KVM + +### Known bugs and limitation: +1. Automated Keymap selection not working in the bootstrap process. You must hit Enter key. +2. Swap automatically created in the bootstrap process, I can't switch it off now. As a result, the minimum image size must be 12Gb or more. +3. It's still dirty, most exceptions during bootstrap still not handled + +### For start adventures: +1. just clone this repo +2. go to the repo directory +3. and type `./runme.sh` in your shell + +### What happened after you run this script: +1. We bootup in-userspace VM and starting the automated installation of Debian 9 with docker and common automation tools (git, ansible, helm, werf). +2. Bootstrapping one node Kubernates cluster with Amsible (LOL we invented minicube without minicube limitations) +3. Installing Nginx.org's Ingress with Helm +4. Installing docker-registry in our shiny Kube (we are lazy of course we use Helm for it) +5. Installing Postgress into Kube 0_o Also with Helm +6. ~~Deploying test service with Helm/Werf~~ +256. ~~PROFIT!!!1~~ diff --git a/README.rst b/README.rst deleted file mode 100644 index 138b5e8..0000000 --- a/README.rst +++ /dev/null @@ -1,55 +0,0 @@ -============================ -django todo |latest-version| -============================ - -|build-status| |health| |downloads| |license| - -django-todo is a pluggable multi-user, multi-group task management and -assignment application for Django. It can serve as anything from a personal -to-do system to a complete, working ticketing system for organizations. - -Documentation -============= - -For documentation, see the django-todo wiki pages: - -- `Overview and screenshots - `_ - -- `Requirements and installation - `_ - -- `Version history - `_ - -Tests -===== - -Serious tests are missing, but we're checking PEP8 conformity of our syntax on -both Python 2 and 3 using ``tox``. You can run the tests locally via:: - - $ python setup.py test - -No prerequisites are required, all test dependencies will be installed -automatically by ``tox`` in virtual environments created on the fly. -Unfortunately, you'll have to install ``virtualenv`` for this to work, though. - -To remove all build files and folders including Python byte code you can run:: - - $ python setup.py clean - -.. |latest-version| image:: https://img.shields.io/pypi/v/django-todo.svg - :alt: Latest version on PyPI - :target: https://pypi.python.org/pypi/django-todo -.. |build-status| image:: https://travis-ci.org/shacker/django-todo.svg - :alt: Build status - :target: https://travis-ci.org/shacker/django-todo -.. |health| image:: https://landscape.io/github/shacker/django-todo/master/landscape.svg?style=flat - :target: https://landscape.io/github/shacker/django-todo/master - :alt: Code health -.. |downloads| image:: https://img.shields.io/pypi/dm/django-todo.svg - :alt: Monthly downloads from PyPI - :target: https://pypi.python.org/pypi/django-todo -.. |license| image:: https://img.shields.io/pypi/l/django-todo.svg - :alt: Software license - :target: https://github.com/shacker/django-todo/blob/master/LICENSE diff --git a/contrib/ansible/ansible.cfg b/contrib/ansible/ansible.cfg new file mode 100644 index 0000000..b21d257 --- /dev/null +++ b/contrib/ansible/ansible.cfg @@ -0,0 +1,10 @@ +[defaults] +allow_world_readable_tmpfiles=True +pipelining=True +retry_files_enabled = False +inventory = inventory +roles_path = roles +library = library +remote_tmp = /root/.ansible/tmp +[connection] +pipelining=True diff --git a/contrib/ansible/bootstrap-node.yml b/contrib/ansible/bootstrap-node.yml new file mode 100644 index 0000000..1928037 --- /dev/null +++ b/contrib/ansible/bootstrap-node.yml @@ -0,0 +1,6 @@ +--- +- name: bootstrap playbook for any k8s machine + hosts: k8s + become: yes + roles: + - bootstrap diff --git a/contrib/ansible/group_vars/all/main.yml b/contrib/ansible/group_vars/all/main.yml new file mode 100644 index 0000000..113cfae --- /dev/null +++ b/contrib/ansible/group_vars/all/main.yml @@ -0,0 +1,11 @@ +k8s_version: '1.17.5' +k8s_first_master_node: 'k8s-demo' +k8s_domain: coins.k8s.demo.ix.gs +k8s_pod_network: '192.168.0.0/16' +k8s_service_network: '10.254.0.0/24' +k8s_controlplane_vip: '100.100.100.15' +k8s_controlplane_address: '{{ k8s_controlplane_vip }}:6443' +k8s_cluster_name: k8s-demo + +cloud_provider: baremetal +ha_enabled: false diff --git a/contrib/ansible/host_vars/k8s-demo.yml b/contrib/ansible/host_vars/k8s-demo.yml new file mode 100644 index 0000000..4e7d5ba --- /dev/null +++ b/contrib/ansible/host_vars/k8s-demo.yml @@ -0,0 +1 @@ +k8s_node_role: 'master' diff --git a/contrib/ansible/init-cluster.yml b/contrib/ansible/init-cluster.yml new file mode 100644 index 0000000..f2e57d6 --- /dev/null +++ b/contrib/ansible/init-cluster.yml @@ -0,0 +1,7 @@ +--- +- name: Init k8s cluster + hosts: 'k8s-demo' + become: yes + max_fail_percentage: 0 + roles: + - init-cluster diff --git a/contrib/ansible/inventory/hosts b/contrib/ansible/inventory/hosts new file mode 100644 index 0000000..e91cbbe --- /dev/null +++ b/contrib/ansible/inventory/hosts @@ -0,0 +1,2 @@ +[k8s] +k8s-demo ansible_connection=local diff --git a/contrib/ansible/keepalived.yml b/contrib/ansible/keepalived.yml new file mode 100644 index 0000000..634312f --- /dev/null +++ b/contrib/ansible/keepalived.yml @@ -0,0 +1,6 @@ +--- +- name: bootstrap playbook for any k8s machine + hosts: k8s-masters + become: yes + roles: + - keepalived diff --git a/contrib/ansible/roles/bootstrap/defaults/main.yml b/contrib/ansible/roles/bootstrap/defaults/main.yml new file mode 100644 index 0000000..4f288b5 --- /dev/null +++ b/contrib/ansible/roles/bootstrap/defaults/main.yml @@ -0,0 +1,6 @@ +kubernetes_apt_release_channel: main +# Note that xenial repo is used for all Debian derivatives at this time. +kubernetes_apt_repository: "deb http://apt.kubernetes.io/ kubernetes-xenial {{ kubernetes_apt_release_channel }}" +## Calico config files +kubernetes_calico_manifest_file: https://docs.projectcalico.org/v3.10/manifests/calico.yaml + diff --git a/contrib/ansible/roles/bootstrap/handlers/main.yml b/contrib/ansible/roles/bootstrap/handlers/main.yml new file mode 100644 index 0000000..4a99fa9 --- /dev/null +++ b/contrib/ansible/roles/bootstrap/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: restart kubelet + service: name=kubelet state=restarted + +- name: restart docker daemon + service: name=docker state=restarted diff --git a/contrib/ansible/roles/bootstrap/tasks/main.yml b/contrib/ansible/roles/bootstrap/tasks/main.yml new file mode 100644 index 0000000..ed0ec24 --- /dev/null +++ b/contrib/ansible/roles/bootstrap/tasks/main.yml @@ -0,0 +1,39 @@ +--- +- name: Ensure dependencies are installed. + apt: + name: + - apt-transport-https + - ca-certificates + state: present + +- name: Add Kubernetes apt key. + apt_key: + url: https://packages.cloud.google.com/apt/doc/apt-key.gpg + state: present + +- name: Add Kubernetes repository. + apt_repository: + repo: "{{ kubernetes_apt_repository }}" + state: present + update_cache: true + +- name: Install kubeadm kubelet kubectl + apt: + pkg: + - kubeadm={{ k8s_version }}-00 + - kubelet={{ k8s_version }}-00 + - kubectl={{ k8s_version }}-00 + - kubernetes-cni=0.7.5-00 + update_cache: yes + notify: restart kubelet + +- name: Add Kubernetes apt preferences file to pin a version. + template: + src: apt-preferences-kubernetes.j2 + dest: /etc/apt/preferences.d/kubernetes + +- name: + template: + src: daemon.json + dest: /etc/docker/daemon.json + notify: restart docker daemon diff --git a/contrib/ansible/roles/bootstrap/templates/apt-preferences-kubernetes.j2 b/contrib/ansible/roles/bootstrap/templates/apt-preferences-kubernetes.j2 new file mode 100644 index 0000000..7709524 --- /dev/null +++ b/contrib/ansible/roles/bootstrap/templates/apt-preferences-kubernetes.j2 @@ -0,0 +1,11 @@ +Package: kubectl +Pin: version {{ k8s_version }}.* +Pin-Priority: 1000 + +Package: kubeadm +Pin: version {{ k8s_version }}.* +Pin-Priority: 1000 + +Package: kubelet +Pin: version {{ k8s_version }}.* +Pin-Priority: 1000 diff --git a/contrib/ansible/roles/bootstrap/templates/daemon.json b/contrib/ansible/roles/bootstrap/templates/daemon.json new file mode 100644 index 0000000..5d18abc --- /dev/null +++ b/contrib/ansible/roles/bootstrap/templates/daemon.json @@ -0,0 +1,8 @@ +{ + "exec-opts": ["native.cgroupdriver=systemd"], + "log-driver": "json-file", + "log-opts": { + "max-size": "100m" + }, + "storage-driver": "overlay2" +} diff --git a/contrib/ansible/roles/init-cluster/tasks/main.yml b/contrib/ansible/roles/init-cluster/tasks/main.yml new file mode 100644 index 0000000..8a7db14 --- /dev/null +++ b/contrib/ansible/roles/init-cluster/tasks/main.yml @@ -0,0 +1,52 @@ +- name: Add Kubeadm config file + template: + src: kubeadm.conf.j2 + dest: /etc/kubeadm.conf + when: k8s_node_role == 'master' + +- name: Init cluster + command: kubeadm init --config /etc/kubeadm.conf --upload-certs --ignore-preflight-errors serviceSubnet + when: ansible_hostname == k8s_first_master_node + +- name: Create kube config directory for root + file: path=/root/.kube state=directory + when: k8s_node_role == 'master' + +- name: Copy Kubernetes admin config to home directory + copy: + src: "/etc/kubernetes/admin.conf" + dest: "/root/.kube/config" +# remote_src: yes +# when: and ansible_hostname == k8s_first_master_node + +- name: Install Calico CNI + command: kubectl apply -f "https://docs.projectcalico.org/v3.13/manifests/calico.yaml" + when: ansible_hostname == k8s_first_master_node + + +- name: Generate join token + command: kubeadm token create --print-join-command + register: join_cmd + delegate_to: '{{ k8s_first_master_node }}' + +# Эта часть не работает +# Правильная команда выглядит так: +# kubeadm join 10.129.64.60:6443 --token --discovery-token-ca-cert-hash --control-plane --certificate-key +# Предыдущий блок генерит сертификат и токен без указания ключа +# Надо пофиксить как будет время +- name: Join rest of master nodes + command: "{{ join_cmd.stdout }} --control-plane" + when: k8s_node_role == 'master' and ha_enabled and ansible_hostname != k8s_first_master_node + ignore_errors: yes + +#- name: Copy Kubernetes admin config to home directory +# copy: +# src: "/etc/kubernetes/admin.conf" +# dest: "/root/.kube/config" +# remote_src: yes +# when: k8s_node_role == 'master' + +- name: Join worker nodes + command: "{{ join_cmd.stdout }}" + when: k8s_node_role == 'worker' + diff --git a/contrib/ansible/roles/init-cluster/templates/kubeadm.conf.j2 b/contrib/ansible/roles/init-cluster/templates/kubeadm.conf.j2 new file mode 100644 index 0000000..84e3ddd --- /dev/null +++ b/contrib/ansible/roles/init-cluster/templates/kubeadm.conf.j2 @@ -0,0 +1,25 @@ +apiVersion: kubelet.config.k8s.io/v1beta1 +kind: KubeletConfiguration +cgroupDriver: systemd +--- +apiVersion: kubeadm.k8s.io/v1beta2 +kind: InitConfiguration +nodeRegistration: +--- +apiVersion: kubeadm.k8s.io/v1beta2 +kind: ClusterConfiguration +kubernetesVersion: {{ k8s_version }} +certificatesDir: /etc/kubernetes/pki +clusterName: {{ k8s_cluster_name }} +controlPlaneEndpoint: {{ k8s_controlplane_address }} +dns: + type: CoreDNS +etcd: + local: + dataDir: /var/lib/etcd +imageRepository: k8s.gcr.io +networking: + dnsDomain: {{ k8s_domain }} + podSubnet: {{ k8s_pod_network }} + serviceSubnet: {{ k8s_service_network }} +scheduler: {} diff --git a/contrib/ansible/roles/keepalived/defaults/main.yml b/contrib/ansible/roles/keepalived/defaults/main.yml new file mode 100644 index 0000000..ed97d53 --- /dev/null +++ b/contrib/ansible/roles/keepalived/defaults/main.yml @@ -0,0 +1 @@ +--- diff --git a/contrib/ansible/roles/keepalived/handlers/main.yml b/contrib/ansible/roles/keepalived/handlers/main.yml new file mode 100644 index 0000000..2ac9fe3 --- /dev/null +++ b/contrib/ansible/roles/keepalived/handlers/main.yml @@ -0,0 +1,3 @@ +--- +- name: restart keepalived + service: name=keepalived state=restarted diff --git a/contrib/ansible/roles/keepalived/tasks/main.yml b/contrib/ansible/roles/keepalived/tasks/main.yml new file mode 100644 index 0000000..4998ba8 --- /dev/null +++ b/contrib/ansible/roles/keepalived/tasks/main.yml @@ -0,0 +1,14 @@ +--- +- name: Install keepalived + apt: + pkg: + - keepalived + state: latest + +- name: Configure keepalived + template: src=keepalived.conf.j2 dest=/etc/keepalived/keepalived.conf + tags: keepalived + notify: restart keepalived + +- name: Start keepalived + service: name=keepalived state=started diff --git a/contrib/ansible/roles/keepalived/templates/keepalived.conf.j2 b/contrib/ansible/roles/keepalived/templates/keepalived.conf.j2 new file mode 100644 index 0000000..dd127ec --- /dev/null +++ b/contrib/ansible/roles/keepalived/templates/keepalived.conf.j2 @@ -0,0 +1,31 @@ + ! Configuration File for keepalived +global_defs { +} + +{#vrrp_script haproxy-check { + script "killall -0 haproxy" + interval 2 + weight 20 +}#} + +vrrp_instance VI_1 { + state {{ keepalived_role }} + interface {{ keepalived_shared_iface }} + virtual_router_id {{ keepalived_router_id }} + {% if keepalived_role.lower() == "master" %} + priority {{ keepalived_priority }} + {% else %} + priority {{ keepalived_backup_priority }} + {% endif %} + advert_int 1 + authentication { + auth_type PASS + auth_pass {{ keepalived_auth_pass }} + } + virtual_ipaddress { + {{ keepalived_shared_ip }} dev {{ keepalived_shared_iface }} label {{ keepalived_shared_iface }}:0 + } +{# track_script { + haproxy-check weight 20 + }#} +} diff --git a/contrib/firstboot.sh b/contrib/firstboot.sh new file mode 100755 index 0000000..cf20fe7 --- /dev/null +++ b/contrib/firstboot.sh @@ -0,0 +1,77 @@ +#!/bin/bash +bootflag='/.manufactured' +groot='/opt/coins-demo' + +k8sDemoWA () { + printf "\033c" + echo 'Disabling swap space...' + sync && swapoff -a && sed -i '/ swap / s/^/#/' /etc/fstab + export KUBECONFIG=/root/.kube/config +} +k8sDeploy () { + echo 'Installing K8s...' + cd ${groot}/contrib/ansible && \ + ansible-playbook bootstrap-node.yml && \ + ansible-playbook init-cluster.yml + # Sometimes it's still not ready on this stage, let's check it just to be sure + while true ; do + echo "Waiting for node up..." + result=$(kubectl get nodes|awk '{print $2}'| tail -1| grep -nE '^Ready') + if [ -z "$result" ] ; then + break + fi + sleep 10 + done +} +InstallCSI () { + helm repo add rimusz https://charts.rimusz.net + helm install rimusz/hostpath-provisioner --generate-name +} +InstallRegistry () { + helm repo add harbor https://helm.goharbor.io + helm install registry harbor/harbor \ + --set expose.tls.enabled=false \ + --set expose.ingress.hosts.core="registry.k8s-demo.ix.gs" \ + --set expose.ingress.hosts.notary="notary.k8s-demo.ix.gs" \ + --set externalURL="http://registry.k8s-demo.ix.gs" \ + --set persistence.storageClass=hostpath \ + --set registry.credentials.username=k8s \ + --set registry.credentials.password=k8s \ + --set notary.enabled=false \ + --set trivy.enabled=false \ + --set clair.enabled=false \ + --set chartmuseum.enabled=false +} +InstallPGSQL () { + helm repo add bitnami https://charts.bitnami.com/bitnami + helm install db bitnami/postgresql \ + --set persistence.storageClass=hostpath \ + --set persistence.size=1Gi +} +InstallIngress () { + # Allow scheduling on our master node + kubectl taint nodes k8s-demo node-role.kubernetes.io/master- + # Installing Ingress + helm repo add nginx-stable https://helm.nginx.com/stable && \ + helm install nginx-stable/nginx-ingress --namespace kube-system --generate-name --set rbac.create=true + # Fix external IP for LB... + kubectl patch svc $(kubectl get svc -n kube-system|grep nginx-ingress|awk '{print $1}') -n kube-system --patch "$(cat ${groot}/contrib/ymls/ingress.fix.yaml)" +} +InstallApp () { + cd ${groot} + export WERF_INSECURE_REGISTRY=true + export WERF_IMAGES_REPO='http://registry.k8s-demo.ix.gs/todo' + werf build --stages-storage :local && \ + werf publish --stages-storage :local --tag-custom stable + werf deploy --stages-storage :local --tag-custom latest --env production --set 'DBPwd=$(kubectl get secret db-postgresql -o jsonpath="{.data.postgresql-password}" | base64 --decode)' +} + +if [ ! -f ${bootflag} ]; then + touch ${bootflag} + k8sDemoWA; + k8sDeploy; + InstallCSI; + InstallIngress; + InstallRegistry; + InstallPGSQL; +fi diff --git a/contrib/k8s-seed.txt b/contrib/k8s-seed.txt new file mode 100644 index 0000000..fb89498 --- /dev/null +++ b/contrib/k8s-seed.txt @@ -0,0 +1,112 @@ +### Keyboard config +d-i debian-installer/locale string en_US.UTF-8 +d-i keyboard-configuration/variant select American English +d-i keyboard-configuration/xkb-keymap select us +d-i keyboard-configuration/toggle select No toggling + +### Network configuration +d-i netcfg/choose_interface select auto +d-i netcfg/get_hostname string unassigned-hostname +d-i netcfg/get_domain string unassigned-domain +d-i netcfg/hostname string k8s-demo +d-i netcfg/wireless_wep string +d-i netcfg/dhcp_hostname string k8s-demo +d-i hw-detect/load_firmware boolean true + +### Mirror settings +d-i mirror/country string manual +d-i mirror/http/hostname string cdn.debian.net +d-i mirror/http/directory string /debian +d-i mirror/http/proxy string + +### Account setup +d-i passwd/root-login boolean false + +# To create a normal user account. +d-i passwd/user-fullname string K8S Admin +d-i passwd/username string k8s +d-i passwd/user-password password K8Sdemo +d-i passwd/user-password-again password K8Sdemo + +# The user account will be added to some standard initial groups. To +# override that, use this. +#d-i passwd/user-default-groups string docker + +### Clock and time zone setup +d-i clock-setup/utc boolean true +d-i time/zone string UTC +d-i clock-setup/ntp boolean true +d-i clock-setup/ntp-server string clock.ix.gs + +### Partitioning +d-i partman-auto/method string regular +d-i partman-lvm/device_remove_lvm boolean true +d-i partman-md/device_remove_md boolean true +d-i partman-lvm/confirm boolean true +d-i partman-lvm/confirm_nooverwrite boolean true +d-i partman-auto/choose_recipe select atomic +d-i partman-basicfilesystems/no_swap boolean true +d-i partman-partitioning/confirm_write_new_label boolean true +d-i partman/choose_partition select finish +d-i partman/confirm boolean true +d-i partman/confirm_nooverwrite boolean true +d-i partman/default_filesystem string xfs + +### Apt setup +d-i apt-setup/non-free boolean true +d-i apt-setup/contrib boolean true +d-i debian-installer/allow_unauthenticated boolean true + + +### Package selection +tasksel tasksel/first multiselect none, ssh-server, standard + +# Individual additional packages to install +d-i pkgsel/include string \ + apt-transport-https gnupg2 ca-certificates curl \ + iptables linux-headers-amd64 git ansible wget \ + gdebi-core + +# Whether to upgrade packages after debootstrap. +# Allowed values: none, safe-upgrade, full-upgrade +d-i pkgsel/upgrade select full-upgrade +popularity-contest popularity-contest/participate boolean true + +### Boot loader installation +d-i grub-installer/only_debian boolean true +d-i grub-installer/with_other_os boolean true +d-i grub-installer/bootdev string default + +### Finishing up the installation +d-i finish-install/reboot_in_progress note +d-i debian-installer/exit/poweroff boolean true + +#### Advanced options +### Running custom commands during the installation +d-i preseed/late_command string in-target /bin/sh -c " \ + echo k8s-demo > /etc/hostname ; \ + sed -i 's/debian/k8s-demo/' /etc/hosts ; \ + update-alternatives --set iptables /usr/sbin/iptables-legacy ; \ + update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy ; \ + update-alternatives --set arptables /usr/sbin/arptables-legacy ; \ + update-alternatives --set ebtables /usr/sbin/ebtables-legacy ; \ + ( systemctl disable nftables && systemctl mask nftables ) ; \ + curl -s https://download.docker.com/linux/debian/gpg | sudo apt-key add - ; \ + curl https://helm.baltorepo.com/organization/signing.asc | sudo apt-key add - ; \ + echo 'deb [arch=amd64] https://download.docker.com/linux/debian/ stretch stable' >/etc/apt/sources.list.d/docker.list ; \ + echo 'deb https://baltocdn.com/helm/stable/debian/ all main' > /etc/apt/sources.list.d/helm-stable-debian.list ; \ + apt-get update ; \ + apt-get install -y \ + docker-ce docker-ce-cli containerd.io helm ; \ + apt-get clean ; \ + curl -L https://dl.bintray.com/flant/werf/v1.1.19+fix10/werf-linux-amd64-v1.1.19+fix10 -o /usr/bin/werf ; \ + chmod +x /usr/bin/werf ; \ + git clone https://git.ix.gs/public/coins-demo.git /opt/coins-demo ; \ + cp /opt/coins-demo/contrib/mfg.service /etc/systemd/system/ ; \ + mkdir -p /lib/systemd/system/docker.service.d ; \ + sed -i 's/sock$/sock --insecure-registry="registry.k8s-demo.ix.gs"/' /lib/systemd/system/docker.service ; \ + systemctl daemon-reload ; \ + systemctl enable mfg.service ; \ + systemctl enable serial-getty@ttyS0.service ; \ + systemctl enable docker.service \ + || true" diff --git a/contrib/mfg.service b/contrib/mfg.service new file mode 100644 index 0000000..4aa829a --- /dev/null +++ b/contrib/mfg.service @@ -0,0 +1,18 @@ +[Unit] +Description=Manufacturing service +After=serial-getty@ttyS0.service + +[Service] +Type=idle +ExecStart=/opt/coins-demo/contrib/firstboot.sh +StandardInput=tty +StandardOutput=tty +TTYPath=/dev/ttyS0 +TTYReset=yes +#TTYVHangup=yes +KillMode=none +TimeoutSec=0 +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target diff --git a/contrib/qemu-system-x86_64 b/contrib/qemu-system-x86_64 new file mode 100755 index 0000000..57598d3 Binary files /dev/null and b/contrib/qemu-system-x86_64 differ diff --git a/contrib/share/qemu/bios-256k.bin b/contrib/share/qemu/bios-256k.bin new file mode 100644 index 0000000..fab9da2 Binary files /dev/null and b/contrib/share/qemu/bios-256k.bin differ diff --git a/contrib/share/qemu/efi-e1000.rom b/contrib/share/qemu/efi-e1000.rom new file mode 100644 index 0000000..776e217 Binary files /dev/null and b/contrib/share/qemu/efi-e1000.rom differ diff --git a/contrib/share/qemu/keymaps/common b/contrib/share/qemu/keymaps/common new file mode 100644 index 0000000..adc56c7 --- /dev/null +++ b/contrib/share/qemu/keymaps/common @@ -0,0 +1,157 @@ +include modifiers + +# +# Top row +# +1 0x2 +2 0x3 +3 0x4 +4 0x5 +5 0x6 +6 0x7 +7 0x8 +8 0x9 +9 0xa +0 0xb +BackSpace 0xe + +# +# QWERTY first row +# +Tab 0xf localstate +ISO_Left_Tab 0xf shift +q 0x10 addupper +w 0x11 addupper +e 0x12 addupper +r 0x13 addupper +t 0x14 addupper +y 0x15 addupper +u 0x16 addupper +i 0x17 addupper +o 0x18 addupper +p 0x19 addupper + +# +# QWERTY second row +# +a 0x1e addupper +s 0x1f addupper +d 0x20 addupper +f 0x21 addupper +g 0x22 addupper +h 0x23 addupper +j 0x24 addupper +k 0x25 addupper +l 0x26 addupper +Return 0x1c localstate + +# +# QWERTY third row +# +z 0x2c addupper +x 0x2d addupper +c 0x2e addupper +v 0x2f addupper +b 0x30 addupper +n 0x31 addupper +m 0x32 addupper + +space 0x39 localstate + +less 0x56 +greater 0x56 shift +bar 0x56 altgr +brokenbar 0x56 shift altgr + +# +# Esc and Function keys +# +Escape 0x1 localstate +F1 0x3b localstate +F2 0x3c localstate +F3 0x3d localstate +F4 0x3e localstate +F5 0x3f localstate +F6 0x40 localstate +F7 0x41 localstate +F8 0x42 localstate +F9 0x43 localstate +F10 0x44 localstate +F11 0x57 localstate +F12 0x58 localstate + +# Printscreen, Scrollock and Pause +# Printscreen really requires four scancodes (0xe0, 0x2a, 0xe0, 0x37), +# but (0xe0, 0x37) seems to work. +Print 0xb7 localstate +Sys_Req 0xb7 localstate +Execute 0xb7 localstate +Scroll_Lock 0x46 + +# +# Insert - PgDown +# +Insert 0xd2 localstate +Delete 0xd3 localstate +Home 0xc7 localstate +End 0xcf localstate +Page_Up 0xc9 localstate +Page_Down 0xd1 localstate + +# +# Arrow keys +# +Left 0xcb localstate +Up 0xc8 localstate +Down 0xd0 localstate +Right 0xcd localstate + +# +# Numpad +# +Num_Lock 0x45 +KP_Divide 0xb5 +KP_Multiply 0x37 +KP_Subtract 0x4a +KP_Add 0x4e +KP_Enter 0x9c + +KP_Decimal 0x53 numlock +KP_Separator 0x53 numlock +KP_Delete 0x53 + +KP_0 0x52 numlock +KP_Insert 0x52 + +KP_1 0x4f numlock +KP_End 0x4f + +KP_2 0x50 numlock +KP_Down 0x50 + +KP_3 0x51 numlock +KP_Next 0x51 + +KP_4 0x4b numlock +KP_Left 0x4b + +KP_5 0x4c numlock +KP_Begin 0x4c + +KP_6 0x4d numlock +KP_Right 0x4d + +KP_7 0x47 numlock +KP_Home 0x47 + +KP_8 0x48 numlock +KP_Up 0x48 + +KP_9 0x49 numlock +KP_Prior 0x49 + +Caps_Lock 0x3a +# +# Inhibited keys +# +Multi_key 0x0 inhibit diff --git a/contrib/share/qemu/keymaps/en-us b/contrib/share/qemu/keymaps/en-us new file mode 100644 index 0000000..f5784bb --- /dev/null +++ b/contrib/share/qemu/keymaps/en-us @@ -0,0 +1,35 @@ +# generated from XKB map us +include common +map 0x409 +exclam 0x02 shift +at 0x03 shift +numbersign 0x04 shift +dollar 0x05 shift +percent 0x06 shift +asciicircum 0x07 shift +ampersand 0x08 shift +asterisk 0x09 shift +parenleft 0x0a shift +parenright 0x0b shift +minus 0x0c +underscore 0x0c shift +equal 0x0d +plus 0x0d shift +bracketleft 0x1a +braceleft 0x1a shift +bracketright 0x1b +braceright 0x1b shift +semicolon 0x27 +colon 0x27 shift +apostrophe 0x28 +quotedbl 0x28 shift +grave 0x29 +asciitilde 0x29 shift +backslash 0x2b +bar 0x2b shift +comma 0x33 +less 0x33 shift +period 0x34 +greater 0x34 shift +slash 0x35 +question 0x35 shift diff --git a/contrib/share/qemu/keymaps/modifiers b/contrib/share/qemu/keymaps/modifiers new file mode 100644 index 0000000..d73b7a6 --- /dev/null +++ b/contrib/share/qemu/keymaps/modifiers @@ -0,0 +1,18 @@ +Shift_R 0x36 +Shift_L 0x2a + +Alt_R 0xb8 +Mode_switch 0xb8 +ISO_Level3_Shift 0xb8 +Alt_L 0x38 + +Control_R 0x9d +Control_L 0x1d + +# Translate Super to Windows keys. +# This is hardcoded. See documentation for details. +Super_R 0xdc +Super_L 0xdb + +# Translate Menu to the Windows Application key. +Menu 0xdd diff --git a/contrib/share/qemu/kvmvapic.bin b/contrib/share/qemu/kvmvapic.bin new file mode 100644 index 0000000..045f5c2 Binary files /dev/null and b/contrib/share/qemu/kvmvapic.bin differ diff --git a/contrib/share/qemu/linuxboot.bin b/contrib/share/qemu/linuxboot.bin new file mode 100644 index 0000000..130103f Binary files /dev/null and b/contrib/share/qemu/linuxboot.bin differ diff --git a/contrib/share/qemu/vgabios-stdvga.bin b/contrib/share/qemu/vgabios-stdvga.bin new file mode 100644 index 0000000..e5e5b14 Binary files /dev/null and b/contrib/share/qemu/vgabios-stdvga.bin differ diff --git a/contrib/ymls/ingress.fix.yaml b/contrib/ymls/ingress.fix.yaml new file mode 100644 index 0000000..691c3b1 --- /dev/null +++ b/contrib/ymls/ingress.fix.yaml @@ -0,0 +1,4 @@ +spec: + externalIPs: + - 100.100.100.15 + loadBalancerIP: 100.100.100.15 diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..64aef39 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,3 @@ +#!/bin/sh +pipenv run python manage.py migrate todo +pipenv run python manage.py runserver 0.0.0.0:8888 diff --git a/local.py b/local.py new file mode 100644 index 0000000..7ef03cf --- /dev/null +++ b/local.py @@ -0,0 +1,26 @@ +from .settings import * +import os + +DEBUG = True + +ALLOWED_HOSTS = ['*'] + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': os.environ['DATABASE_NAME'], + 'HOST': os.environ['DATABASE_HOST'], + 'USER': os.environ['DATABASE_USER'], + 'PASSWORD': os.environ['DATABASE_PASSWORD'], + 'PORT': '', + }, +} + +SECRET_KEY = os.environ['SECRET_KEY'] +EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' + +# TODO-specific settings +TODO_STAFF_ONLY = False +TODO_DEFAULT_LIST_SLUG = 'tickets' +TODO_DEFAULT_ASSIGNEE = None +TODO_PUBLIC_SUBMIT_REDIRECT = '/' diff --git a/runme.sh b/runme.sh new file mode 100755 index 0000000..aa128fe --- /dev/null +++ b/runme.sh @@ -0,0 +1,34 @@ +#!/bin/bash +appdir=$(cd `dirname $0` && pwd) +apptmp='/tmp' +vmsize='16G' +vmmem='4G' +vcpu=`cat /proc/cpuinfo|grep proc|tail -1|awk '{print $NF}'` +k8sdsk="${apptmp}/k8s-demo.raw" +httpboot='http://ftp.debian.org/debian/dists/stretch/main/installer-amd64/current/images/netboot/debian-installer/amd64' +seed='https://git.ix.gs/public/coins-demo/raw/branch/master/contrib/k8s-seed.txt' +qemu="${appdir}/contrib/qemu-system-x86_64 -L ${appdir}/contrib/share/qemu -net nic -net user,net=100.100.100.0/24,hostfwd=tcp::8888-:80 -m ${vmmem} -smp ${vcpu} -localtime -enable-kvm -cpu host,+nx -M pc -vga std -usbdevice tablet -k en-us -hda ${k8sdsk} -boot once=d -nographic" +wget='wget -cq4O' +### +installvm() { + echo "Allocating ${vmsize} disk..." && \ + fallocate -l ${vmsize} ${k8sdsk} && \ + echo "Downloading Linux kernel..." && \ + ${wget} ${apptmp}/kernel ${httpboot}/linux && \ + echo "Downloading Initial ramdisk..." && \ + ${wget} ${apptmp}/initrd ${httpboot}/initrd.gz && \ + echo "Preparing K8S VM..." && \ + ${qemu} -kernel ${apptmp}/kernel -initrd ${apptmp}/initrd -append "console=ttyS0,115200n8 apt-setup/proposed=true nomodeset fb=false priority=critical locale=en_US url=${seed}" && \ + echo "Housekeeping..." && \ + rm -f ${apptmp}/kernel ${apptmp}/initrd +} +runvm() { + echo "Look like we already has VM, let's go" + ${qemu} +} +### +if [ ! -f "${k8sdsk}" ]; then + installvm && runvm; +else + runvm; +fi diff --git a/setup.py b/setup.py deleted file mode 100755 index a708efe..0000000 --- a/setup.py +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/env python - -from glob import glob -from os import remove -from os.path import abspath, dirname, join -from setuptools import setup, find_packages -from setuptools.command.test import test as TestCommand -from shlex import split -from shutil import rmtree -from sys import exit - -import todo as package - - -class Tox(TestCommand): - user_options = [('tox-args=', 'a', "Arguments to pass to tox")] - - def initialize_options(self): - TestCommand.initialize_options(self) - self.tox_args = None - - def finalize_options(self): - TestCommand.finalize_options(self) - self.test_args = [] - self.test_suite = True - - def run_tests(self): - import tox - args = self.tox_args - if args: - args = split(self.tox_args) - errno = tox.cmdline(args=args) - exit(errno) - - -class Clean(TestCommand): - def run(self): - delete_in_root = [ - 'build', - 'dist', - '.eggs', - '*.egg-info', - '.tox', - ] - delete_everywhere = [ - '__pycache__', - '*.pyc', - ] - for candidate in delete_in_root: - rmtree_glob(candidate) - for visible_dir in glob('[A-Za-z0-9]*'): - for candidate in delete_everywhere: - rmtree_glob(join(visible_dir, candidate)) - rmtree_glob(join(visible_dir, '*', candidate)) - rmtree_glob(join(visible_dir, '*', '*', candidate)) - - -def rmtree_glob(file_glob): - for fobj in glob(file_glob): - try: - rmtree(fobj) - print('%s/ removed ...' % fobj) - except OSError: - try: - remove(fobj) - print('%s removed ...' % fobj) - except OSError: - pass - - -def read_file(*pathname): - with open(join(dirname(abspath(__file__)), *pathname)) as f: - return f.read() - - -setup( - name='django-todo', - version=package.__version__, - description=package.__doc__.strip(), - long_description=read_file('README.rst'), - author=package.__author__, - author_email=package.__email__, - url=package.__url__, - license=package.__license__, - packages=find_packages(), - classifiers=[ - 'Development Status :: 4 - Beta', - 'Environment :: Web Environment', - 'Framework :: Django', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 3', - 'Topic :: Office/Business :: Groupware', - 'Topic :: Software Development :: Bug Tracking', - ], - include_package_data=True, - zip_safe=False, - tests_require=['tox'], - install_requires=['django-autoslug', 'unidecode', ], - cmdclass={ - 'clean': Clean, - 'test': Tox, - }, -) diff --git a/todo/__init__.py b/todo/__init__.py deleted file mode 100644 index dd209ab..0000000 --- a/todo/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -""" -A multi-user, multi-group task management and assignment system for Django. -""" -__version__ = '1.6.2' - -__author__ = 'Scot Hacker' -__email__ = 'shacker@birdhouse.org' - -__url__ = 'https://github.com/shacker/django-todo' -__license__ = 'BSD License' diff --git a/todo/admin.py b/todo/admin.py deleted file mode 100644 index 9e28467..0000000 --- a/todo/admin.py +++ /dev/null @@ -1,18 +0,0 @@ -from django.contrib import admin -from todo.models import Item, List, Comment - - -class ItemAdmin(admin.ModelAdmin): - list_display = ('title', 'list', 'priority', 'due_date') - list_filter = ('list',) - ordering = ('priority',) - search_fields = ('name',) - - -class CommentAdmin(admin.ModelAdmin): - list_display = ('author', 'date', 'snippet') - - -admin.site.register(List) -admin.site.register(Comment, CommentAdmin) -admin.site.register(Item, ItemAdmin) diff --git a/todo/forms.py b/todo/forms.py deleted file mode 100644 index f7a015d..0000000 --- a/todo/forms.py +++ /dev/null @@ -1,81 +0,0 @@ -from django import forms -from django.forms import ModelForm -from django.contrib.auth.models import Group -from todo.models import Item, List -from django.contrib.auth import get_user_model - - -class AddListForm(ModelForm): - # The picklist showing allowable groups to which a new list can be added - # determines which groups the user belongs to. This queries the form object - # to derive that list. - def __init__(self, user, *args, **kwargs): - super(AddListForm, self).__init__(*args, **kwargs) - self.fields['group'].queryset = Group.objects.filter(user=user) - - class Meta: - model = List - exclude = [] - - -class AddItemForm(ModelForm): - # The picklist showing the users to which a new task can be assigned - # must find other members of the groups the current list belongs to. - def __init__(self, task_list, *args, **kwargs): - super(AddItemForm, self).__init__(*args, **kwargs) - # print dir(self.fields['list']) - # print self.fields['list'].initial - self.fields['assigned_to'].queryset = get_user_model().objects.filter(groups__in=[task_list.group]) - self.fields['assigned_to'].label_from_instance = \ - lambda obj: "%s (%s)" % (obj.get_full_name(), obj.username) - - due_date = forms.DateField( - required=False, - widget=forms.DateTimeInput(attrs={'class': 'due_date_picker'}) - ) - - title = forms.CharField( - widget=forms.widgets.TextInput(attrs={'size': 35}) - ) - - note = forms.CharField(widget=forms.Textarea(), required=False) - - class Meta: - model = Item - exclude = [] - - -class EditItemForm(ModelForm): - # The picklist showing the users to which a new task can be assigned - # must find other members of the groups the current list belongs to. - def __init__(self, *args, **kwargs): - super(EditItemForm, self).__init__(*args, **kwargs) - self.fields['assigned_to'].queryset = get_user_model().objects.filter(groups__in=[self.instance.list.group]) - - class Meta: - model = Item - exclude = ('created_date', 'created_by',) - - -class AddExternalItemForm(ModelForm): - """Form to allow users who are not part of the GTD system to file a ticket.""" - - title = forms.CharField( - widget=forms.widgets.TextInput(attrs={'size': 35}) - ) - note = forms.CharField( - widget=forms.widgets.Textarea(), - help_text='Foo', - ) - - class Meta: - model = Item - exclude = ('list', 'created_date', 'due_date', 'created_by', 'assigned_to',) - - -class SearchForm(forms.Form): - """Search.""" - - q = forms.CharField( - widget=forms.widgets.TextInput(attrs={'size': 35}) - ) diff --git a/todo/migrations/0001_initial.py b/todo/migrations/0001_initial.py deleted file mode 100644 index c0140e6..0000000 --- a/todo/migrations/0001_initial.py +++ /dev/null @@ -1,78 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations -import datetime -from django.conf import settings - - -class Migration(migrations.Migration): - - dependencies = [ - ('auth', '0001_initial'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='Comment', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('date', models.DateTimeField(default=datetime.datetime.now)), - ('body', models.TextField(blank=True)), - ('author', models.ForeignKey(to=settings.AUTH_USER_MODEL)), - ], - options={ - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='Item', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('title', models.CharField(max_length=140)), - ('created_date', models.DateField(auto_now=True, auto_now_add=True)), - ('due_date', models.DateField(null=True, blank=True)), - ('completed', models.BooleanField(default=None)), - ('completed_date', models.DateField(null=True, blank=True)), - ('note', models.TextField(null=True, blank=True)), - ('priority', models.PositiveIntegerField(max_length=3)), - ('assigned_to', models.ForeignKey(related_name='todo_assigned_to', to=settings.AUTH_USER_MODEL)), - ('created_by', models.ForeignKey(related_name='todo_created_by', to=settings.AUTH_USER_MODEL)), - ], - options={ - 'ordering': ['priority'], - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='List', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('name', models.CharField(max_length=60)), - ('slug', models.SlugField(max_length=60, editable=False)), - ('group', models.ForeignKey(to='auth.Group')), - ], - options={ - 'ordering': ['name'], - 'verbose_name_plural': 'Lists', - }, - bases=(models.Model,), - ), - migrations.AlterUniqueTogether( - name='list', - unique_together=set([('group', 'slug')]), - ), - migrations.AddField( - model_name='item', - name='list', - field=models.ForeignKey(to='todo.List'), - preserve_default=True, - ), - migrations.AddField( - model_name='comment', - name='task', - field=models.ForeignKey(to='todo.Item'), - preserve_default=True, - ), - ] diff --git a/todo/migrations/0002_auto_20150614_2339.py b/todo/migrations/0002_auto_20150614_2339.py deleted file mode 100644 index a346e88..0000000 --- a/todo/migrations/0002_auto_20150614_2339.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('todo', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='item', - name='created_date', - field=models.DateField(auto_now=True), - ), - migrations.AlterField( - model_name='item', - name='priority', - field=models.PositiveIntegerField(), - ), - ] diff --git a/todo/migrations/0003_assignee_optional.py b/todo/migrations/0003_assignee_optional.py deleted file mode 100644 index f5f2625..0000000 --- a/todo/migrations/0003_assignee_optional.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.5 on 2016-04-09 11:11 -from __future__ import unicode_literals - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('todo', '0002_auto_20150614_2339'), - ] - - operations = [ - migrations.AlterField( - model_name='item', - name='assigned_to', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='todo_assigned_to', to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/todo/migrations/__init__.py b/todo/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/todo/models.py b/todo/models.py deleted file mode 100644 index b7b67db..0000000 --- a/todo/models.py +++ /dev/null @@ -1,85 +0,0 @@ -from __future__ import unicode_literals -import datetime - -from django.db import models -from django.contrib.auth.models import Group -from django.core.urlresolvers import reverse -from django.utils.encoding import python_2_unicode_compatible -from django.conf import settings -from autoslug import AutoSlugField - - -@python_2_unicode_compatible -class List(models.Model): - name = models.CharField(max_length=60) - slug = AutoSlugField(populate_from='name', editable=False, always_update=True) - group = models.ForeignKey(Group) - - def __str__(self): - return self.name - - def incomplete_tasks(self): - # Count all incomplete tasks on the current list instance - return Item.objects.filter(list=self, completed=0) - - class Meta: - ordering = ["name"] - verbose_name_plural = "Lists" - - # Prevents (at the database level) creation of two lists with the same name in the same group - unique_together = ("group", "slug") - - -@python_2_unicode_compatible -class Item(models.Model): - title = models.CharField(max_length=140) - list = models.ForeignKey(List) - created_date = models.DateField(auto_now=True) - due_date = models.DateField(blank=True, null=True, ) - completed = models.BooleanField(default=None) - completed_date = models.DateField(blank=True, null=True) - created_by = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='todo_created_by') - assigned_to = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, related_name='todo_assigned_to') - note = models.TextField(blank=True, null=True) - priority = models.PositiveIntegerField() - - # Has due date for an instance of this object passed? - def overdue_status(self): - "Returns whether the item's due date has passed or not." - if self.due_date and datetime.date.today() > self.due_date: - return 1 - - def __str__(self): - return self.title - - def get_absolute_url(self): - return reverse('todo-task_detail', kwargs={'task_id': self.id, }) - - # Auto-set the item creation / completed date - def save(self): - # If Item is being marked complete, set the completed_date - if self.completed: - self.completed_date = datetime.datetime.now() - super(Item, self).save() - - class Meta: - ordering = ["priority"] - - -@python_2_unicode_compatible -class Comment(models.Model): - """ - Not using Django's built-in comments because we want to be able to save - a comment and change task details at the same time. Rolling our own since it's easy. - """ - author = models.ForeignKey(settings.AUTH_USER_MODEL) - task = models.ForeignKey(Item) - date = models.DateTimeField(default=datetime.datetime.now) - body = models.TextField(blank=True) - - def snippet(self): - # Define here rather than in __str__ so we can use it in the admin list_display - return "{author} - {snippet}...".format(author=self.author, snippet=self.body[:35]) - - def __str__(self): - return self.snippet diff --git a/todo/settings.py b/todo/settings.py deleted file mode 100644 index e9231b5..0000000 --- a/todo/settings.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.conf import settings - - -STAFF_ONLY = getattr(settings, 'TODO_STAFF_ONLY', False) -DEFAULT_LIST_ID = getattr(settings, 'TODO_DEFAULT_LIST_ID', 1) -DEFAULT_ASSIGNEE = getattr(settings, 'TODO_DEFAULT_ASSIGNEE', None) -PUBLIC_SUBMIT_REDIRECT = getattr(settings, 'TODO_PUBLIC_SUBMIT_REDIRECT', '/') diff --git a/todo/static/todo/css/styles.css b/todo/static/todo/css/styles.css deleted file mode 100644 index a20c4b8..0000000 --- a/todo/static/todo/css/styles.css +++ /dev/null @@ -1,66 +0,0 @@ -/*Distributed*/ - -ul.messages li { - color: green; - font-weight: bold; -} - -.overdue { - color: #9A2441; - font-weight: bold; -} - -/* Lighter font for completed items */ -#completed li { - color: gray; -} - -a.todo { - text-decoration: none; - color: #474747; -} - -a.showlink { - text-decoration: underline; -} - - -label { - display: block; - font-weight: bold; -} - -input { - color: #3A3A3A; - font-family: Verdana; - font-size: 14px; -} - -input[type='text'] { - width: 300px; -} - -input#id_priority { - width: 30px; -} - - -.todo-break { - margin-top: 30px; - border-top: 1px dotted gray; -} - -table.nocolor, table.nocolor tr, table.nocolor td { - background-color: transparent; -} - - -.minor { - font-style: italic; - font-size: 0.8em; -} - -.task_note, .task_comments { - width: 70%; - overflow: visible; -} diff --git a/todo/static/todo/css/ui.datepicker.css b/todo/static/todo/css/ui.datepicker.css deleted file mode 100644 index 49b00c0..0000000 --- a/todo/static/todo/css/ui.datepicker.css +++ /dev/null @@ -1,213 +0,0 @@ -/* Main Style Sheet for jQuery UI date picker */ -#ui-datepicker-div, .ui-datepicker-inline { - font-family: Arial, Helvetica, sans-serif; - font-size: 14px; - padding: 0; - margin: 0; - background: #ddd; - width: 185px; -} -#ui-datepicker-div { - display: none; - border: 1px solid #777; - z-index: 100; /*must have*/ -} -.ui-datepicker-inline { - float: left; - display: block; - border: 0; -} -.ui-datepicker-rtl { - direction: rtl; -} -.ui-datepicker-dialog { - padding: 5px !important; - border: 4px ridge #ddd !important; -} -.ui-datepicker-disabled { - position: absolute; - z-index: 100; - background-color: white; - opacity: 0.5; -} -button.ui-datepicker-trigger { - width: 25px; -} -img.ui-datepicker-trigger { - margin: 2px; - vertical-align: middle; -} -.ui-datepicker-prompt { - float: left; - padding: 2px; - background: #ddd; - color: #000; -} -* html .ui-datepicker-prompt { - width: 185px; -} -.ui-datepicker-control, .ui-datepicker-links, .ui-datepicker-header, .ui-datepicker { - clear: both; - float: left; - width: 100%; - color: #fff; -} -.ui-datepicker-control { - background: #400; - padding: 2px 0px; -} -.ui-datepicker-links { - background: #000; - padding: 2px 0px; -} -.ui-datepicker-control, .ui-datepicker-links { - font-weight: bold; - font-size: 80%; -} -.ui-datepicker-links label { /* disabled links */ - padding: 2px 5px; - color: #888; -} -.ui-datepicker-clear, .ui-datepicker-prev { - float: left; - width: 34%; -} -.ui-datepicker-rtl .ui-datepicker-clear, .ui-datepicker-rtl .ui-datepicker-prev { - float: right; - text-align: right; -} -.ui-datepicker-current { - float: left; - width: 30%; - text-align: center; -} -.ui-datepicker-close, .ui-datepicker-next { - float: right; - width: 34%; - text-align: right; -} -.ui-datepicker-rtl .ui-datepicker-close, .ui-datepicker-rtl .ui-datepicker-next { - float: left; - text-align: left; -} -.ui-datepicker-header { - padding: 1px 0 3px; - background: #333; - text-align: center; - font-weight: bold; - height: 1.3em; -} -.ui-datepicker-header select { - background: #333; - color: #fff; - border: 0px; - font-weight: bold; -} -.ui-datepicker { - background: #ccc; - text-align: center; - font-size: 100%; -} -.ui-datepicker a { - display: block; - width: 100%; -} -.ui-datepicker-title-row { - background: #777; -} -.ui-datepicker-days-row { - background: #eee; - color: #666; -} -.ui-datepicker-week-col { - background: #777; - color: #fff; -} -.ui-datepicker-days-cell { - color: #000; - border: 1px solid #ddd; -} -.ui-datepicker-days-cell a{ - display: block; -} -.ui-datepicker-week-end-cell { - background: #ddd; -} -.ui-datepicker-title-row .ui-datepicker-week-end-cell { - background: #777; -} -.ui-datepicker-days-cell-over { - background: #fff; - border: 1px solid #777; -} -.ui-datepicker-unselectable { - color: #888; -} -.ui-datepicker-today { - background: #fcc !important; -} -.ui-datepicker-current-day { - background: #999 !important; -} -.ui-datepicker-status { - background: #ddd; - width: 100%; - font-size: 80%; - text-align: center; -} - -/* ________ Datepicker Links _______ - -** Reset link properties and then override them with !important */ -#ui-datepicker-div a, .ui-datepicker-inline a { - cursor: pointer; - margin: 0; - padding: 0; - background: none; - color: #000; -} -.ui-datepicker-inline .ui-datepicker-links a { - padding: 0 5px !important; -} -.ui-datepicker-control a, .ui-datepicker-links a { - padding: 2px 5px !important; - color: #eee !important; -} -.ui-datepicker-title-row a { - color: #eee !important; -} -.ui-datepicker-control a:hover { - background: #fdd !important; - color: #333 !important; -} -.ui-datepicker-links a:hover, .ui-datepicker-title-row a:hover { - background: #ddd !important; - color: #333 !important; -} - -/* ___________ MULTIPLE MONTHS _________*/ - -.ui-datepicker-multi .ui-datepicker { - border: 1px solid #777; -} -.ui-datepicker-one-month { - float: left; - width: 185px; -} -.ui-datepicker-new-row { - clear: left; -} - -/* ___________ IE6 IFRAME FIX ________ */ - -.ui-datepicker-cover { - display: none; /*sorry for IE5*/ - display/**/: block; /*sorry for IE5*/ - position: absolute; /*must have*/ - z-index: -1; /*must have*/ - filter: mask(); /*must have*/ - top: -4px; /*must have*/ - left: -4px; /*must have*/ - width: 200px; /*must have*/ - height: 200px; /*must have*/ -} diff --git a/todo/static/todo/js/jquery.tablednd_0_5.js b/todo/static/todo/js/jquery.tablednd_0_5.js deleted file mode 100644 index d4c9fec..0000000 --- a/todo/static/todo/js/jquery.tablednd_0_5.js +++ /dev/null @@ -1,382 +0,0 @@ -/** - * TableDnD plug-in for JQuery, allows you to drag and drop table rows - * You can set up various options to control how the system will work - * Copyright (c) Denis Howlett - * Licensed like jQuery, see http://docs.jquery.com/License. - * - * Configuration options: - * - * onDragStyle - * This is the style that is assigned to the row during drag. There are limitations to the styles that can be - * associated with a row (such as you can't assign a border--well you can, but it won't be - * displayed). (So instead consider using onDragClass.) The CSS style to apply is specified as - * a map (as used in the jQuery css(...) function). - * onDropStyle - * This is the style that is assigned to the row when it is dropped. As for onDragStyle, there are limitations - * to what you can do. Also this replaces the original style, so again consider using onDragClass which - * is simply added and then removed on drop. - * onDragClass - * This class is added for the duration of the drag and then removed when the row is dropped. It is more - * flexible than using onDragStyle since it can be inherited by the row cells and other content. The default - * is class is tDnD_whileDrag. So to use the default, simply customise this CSS class in your - * stylesheet. - * onDrop - * Pass a function that will be called when the row is dropped. The function takes 2 parameters: the table - * and the row that was dropped. You can work out the new order of the rows by using - * table.rows. - * onDragStart - * Pass a function that will be called when the user starts dragging. The function takes 2 parameters: the - * table and the row which the user has started to drag. - * onAllowDrop - * Pass a function that will be called as a row is over another row. If the function returns true, allow - * dropping on that row, otherwise not. The function takes 2 parameters: the dragged row and the row under - * the cursor. It returns a boolean: true allows the drop, false doesn't allow it. - * scrollAmount - * This is the number of pixels to scroll if the user moves the mouse cursor to the top or bottom of the - * window. The page should automatically scroll up or down as appropriate (tested in IE6, IE7, Safari, FF2, - * FF3 beta - * dragHandle - * This is the name of a class that you assign to one or more cells in each row that is draggable. If you - * specify this class, then you are responsible for setting cursor: move in the CSS and only these cells - * will have the drag behaviour. If you do not specify a dragHandle, then you get the old behaviour where - * the whole row is draggable. - * - * Other ways to control behaviour: - * - * Add class="nodrop" to any rows for which you don't want to allow dropping, and class="nodrag" to any rows - * that you don't want to be draggable. - * - * Inside the onDrop method you can also call $.tableDnD.serialize() this returns a string of the form - * []=&[]= so that you can send this back to the server. The table must have - * an ID as must all the rows. - * - * Other methods: - * - * $("...").tableDnDUpdate() - * Will update all the matching tables, that is it will reapply the mousedown method to the rows (or handle cells). - * This is useful if you have updated the table rows using Ajax and you want to make the table draggable again. - * The table maintains the original configuration (so you don't have to specify it again). - * - * $("...").tableDnDSerialize() - * Will serialize and return the serialized string as above, but for each of the matching tables--so it can be - * called from anywhere and isn't dependent on the currentTable being set up correctly before calling - * - * Known problems: - * - Auto-scoll has some problems with IE7 (it scrolls even when it shouldn't), work-around: set scrollAmount to 0 - * - * Version 0.2: 2008-02-20 First public version - * Version 0.3: 2008-02-07 Added onDragStart option - * Made the scroll amount configurable (default is 5 as before) - * Version 0.4: 2008-03-15 Changed the noDrag/noDrop attributes to nodrag/nodrop classes - * Added onAllowDrop to control dropping - * Fixed a bug which meant that you couldn't set the scroll amount in both directions - * Added serialize method - * Version 0.5: 2008-05-16 Changed so that if you specify a dragHandle class it doesn't make the whole row - * draggable - * Improved the serialize method to use a default (and settable) regular expression. - * Added tableDnDupate() and tableDnDSerialize() to be called when you are outside the table - */ -jQuery.tableDnD = { - /** Keep hold of the current table being dragged */ - currentTable : null, - /** Keep hold of the current drag object if any */ - dragObject: null, - /** The current mouse offset */ - mouseOffset: null, - /** Remember the old value of Y so that we don't do too much processing */ - oldY: 0, - - /** Actually build the structure */ - build: function(options) { - // Set up the defaults if any - - this.each(function() { - // This is bound to each matching table, set up the defaults and override with user options - this.tableDnDConfig = jQuery.extend({ - onDragStyle: null, - onDropStyle: null, - // Add in the default class for whileDragging - onDragClass: "tDnD_whileDrag", - onDrop: null, - onDragStart: null, - scrollAmount: 5, - serializeRegexp: /[^\-]*$/, // The regular expression to use to trim row IDs - serializeParamName: null, // If you want to specify another parameter name instead of the table ID - dragHandle: null // If you give the name of a class here, then only Cells with this class will be draggable - }, options || {}); - // Now make the rows draggable - jQuery.tableDnD.makeDraggable(this); - }); - - // Now we need to capture the mouse up and mouse move event - // We can use bind so that we don't interfere with other event handlers - jQuery(document) - .bind('mousemove', jQuery.tableDnD.mousemove) - .bind('mouseup', jQuery.tableDnD.mouseup); - - // Don't break the chain - return this; - }, - - /** This function makes all the rows on the table draggable apart from those marked as "NoDrag" */ - makeDraggable: function(table) { - var config = table.tableDnDConfig; - if (table.tableDnDConfig.dragHandle) { - // We only need to add the event to the specified cells - var cells = jQuery("td."+table.tableDnDConfig.dragHandle, table); - cells.each(function() { - // The cell is bound to "this" - jQuery(this).mousedown(function(ev) { - jQuery.tableDnD.dragObject = this.parentNode; - jQuery.tableDnD.currentTable = table; - jQuery.tableDnD.mouseOffset = jQuery.tableDnD.getMouseOffset(this, ev); - if (config.onDragStart) { - // Call the onDrop method if there is one - config.onDragStart(table, this); - } - return false; - }); - }) - } else { - // For backwards compatibility, we add the event to the whole row - var rows = jQuery("tr", table); // get all the rows as a wrapped set - rows.each(function() { - // Iterate through each row, the row is bound to "this" - var row = jQuery(this); - if (! row.hasClass("nodrag")) { - row.mousedown(function(ev) { - if (ev.target.tagName == "TD") { - jQuery.tableDnD.dragObject = this; - jQuery.tableDnD.currentTable = table; - jQuery.tableDnD.mouseOffset = jQuery.tableDnD.getMouseOffset(this, ev); - if (config.onDragStart) { - // Call the onDrop method if there is one - config.onDragStart(table, this); - } - return false; - } - }).css("cursor", "move"); // Store the tableDnD object - } - }); - } - }, - - updateTables: function() { - this.each(function() { - // this is now bound to each matching table - if (this.tableDnDConfig) { - jQuery.tableDnD.makeDraggable(this); - } - }) - }, - - /** Get the mouse coordinates from the event (allowing for browser differences) */ - mouseCoords: function(ev){ - if(ev.pageX || ev.pageY){ - return {x:ev.pageX, y:ev.pageY}; - } - return { - x:ev.clientX + document.body.scrollLeft - document.body.clientLeft, - y:ev.clientY + document.body.scrollTop - document.body.clientTop - }; - }, - - /** Given a target element and a mouse event, get the mouse offset from that element. - To do this we need the element's position and the mouse position */ - getMouseOffset: function(target, ev) { - ev = ev || window.event; - - var docPos = this.getPosition(target); - var mousePos = this.mouseCoords(ev); - return {x:mousePos.x - docPos.x, y:mousePos.y - docPos.y}; - }, - - /** Get the position of an element by going up the DOM tree and adding up all the offsets */ - getPosition: function(e){ - var left = 0; - var top = 0; - /** Safari fix -- thanks to Luis Chato for this! */ - if (e.offsetHeight == 0) { - /** Safari 2 doesn't correctly grab the offsetTop of a table row - this is detailed here: - http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari/ - the solution is likewise noted there, grab the offset of a table cell in the row - the firstChild. - note that firefox will return a text node as a first child, so designing a more thorough - solution may need to take that into account, for now this seems to work in firefox, safari, ie */ - e = e.firstChild; // a table cell - } - - while (e.offsetParent){ - left += e.offsetLeft; - top += e.offsetTop; - e = e.offsetParent; - } - - left += e.offsetLeft; - top += e.offsetTop; - - return {x:left, y:top}; - }, - - mousemove: function(ev) { - if (jQuery.tableDnD.dragObject == null) { - return; - } - - var dragObj = jQuery(jQuery.tableDnD.dragObject); - var config = jQuery.tableDnD.currentTable.tableDnDConfig; - var mousePos = jQuery.tableDnD.mouseCoords(ev); - var y = mousePos.y - jQuery.tableDnD.mouseOffset.y; - //auto scroll the window - var yOffset = window.pageYOffset; - if (document.all) { - // Windows version - //yOffset=document.body.scrollTop; - if (typeof document.compatMode != 'undefined' && - document.compatMode != 'BackCompat') { - yOffset = document.documentElement.scrollTop; - } - else if (typeof document.body != 'undefined') { - yOffset=document.body.scrollTop; - } - - } - - if (mousePos.y-yOffset < config.scrollAmount) { - window.scrollBy(0, -config.scrollAmount); - } else { - var windowHeight = window.innerHeight ? window.innerHeight - : document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight; - if (windowHeight-(mousePos.y-yOffset) < config.scrollAmount) { - window.scrollBy(0, config.scrollAmount); - } - } - - - if (y != jQuery.tableDnD.oldY) { - // work out if we're going up or down... - var movingDown = y > jQuery.tableDnD.oldY; - // update the old value - jQuery.tableDnD.oldY = y; - // update the style to show we're dragging - if (config.onDragClass) { - dragObj.addClass(config.onDragClass); - } else { - dragObj.css(config.onDragStyle); - } - // If we're over a row then move the dragged row to there so that the user sees the - // effect dynamically - var currentRow = jQuery.tableDnD.findDropTargetRow(dragObj, y); - if (currentRow) { - // TODO worry about what happens when there are multiple TBODIES - if (movingDown && jQuery.tableDnD.dragObject != currentRow) { - jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject, currentRow.nextSibling); - } else if (! movingDown && jQuery.tableDnD.dragObject != currentRow) { - jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject, currentRow); - } - } - } - - return false; - }, - - /** We're only worried about the y position really, because we can only move rows up and down */ - findDropTargetRow: function(draggedRow, y) { - var rows = jQuery.tableDnD.currentTable.rows; - for (var i=0; i rowY - rowHeight) && (y < (rowY + rowHeight))) { - // that's the row we're over - // If it's the same as the current row, ignore it - if (row == draggedRow) {return null;} - var config = jQuery.tableDnD.currentTable.tableDnDConfig; - if (config.onAllowDrop) { - if (config.onAllowDrop(draggedRow, row)) { - return row; - } else { - return null; - } - } else { - // If a row has nodrop class, then don't allow dropping (inspired by John Tarr and Famic) - var nodrop = jQuery(row).hasClass("nodrop"); - if (! nodrop) { - return row; - } else { - return null; - } - } - return row; - } - } - return null; - }, - - mouseup: function(e) { - if (jQuery.tableDnD.currentTable && jQuery.tableDnD.dragObject) { - var droppedRow = jQuery.tableDnD.dragObject; - var config = jQuery.tableDnD.currentTable.tableDnDConfig; - // If we have a dragObject, then we need to release it, - // The row will already have been moved to the right place so we just reset stuff - if (config.onDragClass) { - jQuery(droppedRow).removeClass(config.onDragClass); - } else { - jQuery(droppedRow).css(config.onDropStyle); - } - jQuery.tableDnD.dragObject = null; - if (config.onDrop) { - // Call the onDrop method if there is one - config.onDrop(jQuery.tableDnD.currentTable, droppedRow); - } - jQuery.tableDnD.currentTable = null; // let go of the table too - } - }, - - serialize: function() { - if (jQuery.tableDnD.currentTable) { - return jQuery.tableDnD.serializeTable(jQuery.tableDnD.currentTable); - } else { - return "Error: No Table id set, you need to set an id on your table and every row"; - } - }, - - serializeTable: function(table) { - var result = ""; - var tableId = table.id; - var rows = table.rows; - for (var i=0; i 0) result += "&"; - var rowId = rows[i].id; - if (rowId && rowId && table.tableDnDConfig && table.tableDnDConfig.serializeRegexp) { - rowId = rowId.match(table.tableDnDConfig.serializeRegexp)[0]; - } - - result += tableId + '[]=' + rowId; - } - return result; - }, - - serializeTables: function() { - var result = ""; - this.each(function() { - // this is now bound to each matching table - result += jQuery.tableDnD.serializeTable(this); - }); - return result; - } - -} - -jQuery.fn.extend( - { - tableDnD : jQuery.tableDnD.build, - tableDnDUpdate : jQuery.tableDnD.updateTables, - tableDnDSerialize: jQuery.tableDnD.serializeTables - } -); \ No newline at end of file diff --git a/todo/templates/todo/add_external_task.html b/todo/templates/todo/add_external_task.html deleted file mode 100644 index a18d56f..0000000 --- a/todo/templates/todo/add_external_task.html +++ /dev/null @@ -1,59 +0,0 @@ -{% extends "todo/base.html" %} -{% block page_heading %}{% endblock %} -{% block title %}File Ticket{% endblock %} - -{% block content %} - -

{{ task }}

- -
- {% csrf_token %} - - {% if task.note %} -
Note: {{ task.note|safe|urlize|linebreaks }}
- {% endif %} - -
-

File Trouble Ticket

-

Trouble with a computer or other technical system at the J-School?
- Use this form to report the difficulty - we'll get right back to you.

- - {% if form.errors %} - - {% for error in form.errors %} -
    -
  • The {{ error|escape }} field is required.
  • -
- {% endfor %} -
- - {% endif %} - - - - - - - - - - - - - - - - -
Summary:{{ form.title }}
- Include the workstation number in your summary, e.g.
- "Radio Lab # 4: Purple smoke pouring out the back." -
Note:{{ form.note }}
- Please describe the problem. -
Priority:{{ form.priority }}
- Enter a number between 1 and 5,
- where 5 is highest ("Computer is on fire = True"). -
-

-
-
-{% endblock %} diff --git a/todo/templates/todo/add_list.html b/todo/templates/todo/add_list.html deleted file mode 100644 index 6fcb22a..0000000 --- a/todo/templates/todo/add_list.html +++ /dev/null @@ -1,16 +0,0 @@ -{% extends "todo/base.html" %} - -{% block page_heading %}{% endblock %} -{% block title %}Add Todo List{% endblock %} - -{% block content %} - -

Add a list:

- -
- {% csrf_token %} - {{ form }}
-

-
- -{% endblock %} diff --git a/todo/templates/todo/base.html b/todo/templates/todo/base.html deleted file mode 100644 index 3e128f6..0000000 --- a/todo/templates/todo/base.html +++ /dev/null @@ -1,15 +0,0 @@ -{% extends "base.html" %} -{% load staticfiles %} - -{% block extrahead %} - - - - -{% endblock extrahead %} diff --git a/todo/templates/todo/del_list.html b/todo/templates/todo/del_list.html deleted file mode 100644 index 78c429c..0000000 --- a/todo/templates/todo/del_list.html +++ /dev/null @@ -1,32 +0,0 @@ -{% extends "todo/base.html" %} - -{% block title %}{{ list_title }} to-do items{% endblock %} - -{% block content %} - - {% if user.is_staff %} -

Delete entire list: {{ list.name }} ?

- -

Category tally:

- -
    -
  • Incomplete: {{ item_count_undone }}
  • -
  • Complete: {{ item_count_done }}
  • -
  • Total: {{ item_count_total }}
  • -
- -

... all of which will be irretrievably blown away. Are you sure you want to do that?

- -
- {% csrf_token %} - -

-
- - Return to list: {{ list.name }} - - {% else %} -

Sorry, you don't have permission to delete lists. Please contact your group administrator.

- {% endif %} - -{% endblock %} diff --git a/todo/templates/todo/email/assigned_body.txt b/todo/templates/todo/email/assigned_body.txt deleted file mode 100644 index 51d143d..0000000 --- a/todo/templates/todo/email/assigned_body.txt +++ /dev/null @@ -1,20 +0,0 @@ -Dear {{ task.assigned_to.first_name }} - - -A new task on the list {{ task.list.name }} has been assigned to you by {{ task.created_by.first_name }} {{ task.created_by.last_name }}: - -{{ task.title }} - -{% if task.note %} -{% autoescape off %} -Note: {{ task.note }} -{% endautoescape %} -{% endif %} - - - - -Task details/comments: -http://{{ site }}{% url 'todo-task_detail' task.id %} - -List {{ task.list.name }}: -http://{{ site }}{% url 'todo-incomplete_tasks' task.list.id task.list.slug %} diff --git a/todo/templates/todo/email/assigned_subject.txt b/todo/templates/todo/email/assigned_subject.txt deleted file mode 100644 index b6cc9db..0000000 --- a/todo/templates/todo/email/assigned_subject.txt +++ /dev/null @@ -1 +0,0 @@ -GTD: New task - {% autoescape off %}Note: {{ task.title }}{% endautoescape %} \ No newline at end of file diff --git a/todo/templates/todo/email/newcomment_body.txt b/todo/templates/todo/email/newcomment_body.txt deleted file mode 100644 index 2a1df3c..0000000 --- a/todo/templates/todo/email/newcomment_body.txt +++ /dev/null @@ -1,16 +0,0 @@ -A new task comment has been added. - -Task: {{ task.title }} -Commenter: {{ user.first_name }} {{ user.last_name }} - -Comment: -{% autoescape off %} -{{ body }} -{% endautoescape %} - -Task details/comments: -https://{{ site }}{% url 'todo-task_detail' task.id %} - -List {{ task.list.name }}: -https://{{ site }}{% url 'todo-incomplete_tasks' task.list.id task.list.slug %} - diff --git a/todo/templates/todo/list_lists.html b/todo/templates/todo/list_lists.html deleted file mode 100644 index 459e842..0000000 --- a/todo/templates/todo/list_lists.html +++ /dev/null @@ -1,24 +0,0 @@ -{% extends "todo/base.html" %} - -{% block title %}{{ list_title }} Todo Lists{% endblock %} - -{% block content %} - -

Todo Lists

- -

{{ item_count }} items in {{ list_count }} lists

- - {% regroup list_list by group as section_list %} - - {% for group in section_list %} -

{{ group.grouper }}

-
    - {% for item in group.list %} -
  • {{ item.name }} ({{ item.incomplete_tasks.count }}/{{ item.item_set.count }})
  • - {% endfor %} -
- {% endfor %} - -

Create new todo list

- -{% endblock %} diff --git a/todo/templates/todo/search_results.html b/todo/templates/todo/search_results.html deleted file mode 100644 index c1fdbe4..0000000 --- a/todo/templates/todo/search_results.html +++ /dev/null @@ -1,26 +0,0 @@ -{% extends "todo/base.html" %} - -{% block title %}Search results{% endblock %} - -{% block content_title %} -

Search

-{% endblock %} - -{% block content %} - {% if found_items %} -

{{found_items.count}} search results for term: "{{ query_string }}"

-
- {% for f in found_items %} -

{{ f.title }}
- - On list: {{ f.list.name }}
- Assigned to: {% if f.assigned_to %}{{ f.assigned_to }}{% else %}Anyone{% endif %} (created by: {{ f.created_by }})
- Complete: {{ f.completed|yesno:"Yes,No" }} -
-

- {% endfor %} -
- {% else %} -

No results to show, sorry.

- {% endif %} -{% endblock %} diff --git a/todo/templates/todo/view_list.html b/todo/templates/todo/view_list.html deleted file mode 100644 index 1a27d38..0000000 --- a/todo/templates/todo/view_list.html +++ /dev/null @@ -1,166 +0,0 @@ -{% extends "todo/base.html" %} - -{% block title %}Todo List: {{ list.name }}{% endblock %} - -{% block content %} - - - {% if list_slug == "mine" %} -

Tasks assigned to {{ request.user }}

- {% elif auth_ok %} -

Tasks filed under "{{ list.name }}"

-

This list belongs to group {{ list.group }}

- {% endif %} - - {% if auth_ok %} -
- {% csrf_token %} - - {# Only show task adder if viewing a proper list #} - {% if list_slug != "mine" %} -

→ Click to add task ←

- -
- - - - - - - - - - - - - - -
{{ form.title.errors }}{{ form.due_date.errors }}
{{ form.title }} {{ form.due_date }} {{ form.assigned_to }}
- {{ form.note }} -

*Email notifications will only be sent if task is assigned to someone besides yourself.

-
- - - - - -

-
- {% endif %} - - {% if not view_completed %} - -

Incomplete tasks :: Drag rows to set priorities

- - - - - - - - - - - - {% if list_slug == "mine" %} - - {% endif %} - - - {% for task in task_list %} - - - - - - - - - - {% if list_slug == "mine" %} - - {% endif %} - - - {% endfor %} -
DoneTaskCreatedDue onOwnerAssignedNoteCommListDel
{{ task.title|truncatewords:20 }}{{ task.created_date|date:"m/d/Y" }} - {% if task.overdue_status %}{% endif %} - {{ task.due_date|date:"m/d/Y" }} - {% if task.overdue_status %}{% endif %} - {{ task.created_by }}{% if task.assigned_to %}{{ task.assigned_to }}{% else %}Anyone{% endif %}{% if task.note %}≈{% endif %} {% if task.comment_set.all.count != 0 %}{{ task.comment_set.all.count }}{% endif %}{{ task.list }}
- -

-

View completed tasks

- - {% else %} - -

Completed tasks

- - - - - - - - - - {% if list_slug == "mine" %} - - {% endif %} - - - - {% for task in completed_list %} - - - - - - - - - {% endfor %} - -
UndoTaskCreatedCompleted onNoteCommListDel
{{ task.title|truncatewords:20 }}{{ task.created_date|date:"m/d/Y" }}{{ task.completed_date|date:"m/d/Y" }}{% if task.note %}≈{% endif %} {% if task.comment_set.all.count != 0 %}{{ task.comment_set.all.count }}{% endif %} -
-

-
-

View incomplete tasks

- {% endif %} - - {% if user.is_staff %} - {% if list_slug != "mine" %} -

Delete this list

- {% endif %} - {% endif %} - - {% endif %} -{% endblock %} diff --git a/todo/templates/todo/view_task.html b/todo/templates/todo/view_task.html deleted file mode 100644 index aec27ad..0000000 --- a/todo/templates/todo/view_task.html +++ /dev/null @@ -1,103 +0,0 @@ -{% extends "todo/base.html" %} - -{% block title %}Task: {{ task.title }}{% endblock %} - -{% block content %} - - - - {% if auth_ok %} - -

{{ task }}

- -
- {% csrf_token %} - -

→ Click to edit details ←

- -

- In list: {{ task.list }}
- Assigned to: {% if task.assigned_to %}{{ task.assigned_to.get_full_name }}{% else %}Anyone{% endif %}
- Created by: {{ task.created_by.first_name }} {{ task.created_by.last_name }}
- Due date: {{ task.due_date }}
- Completed: {{ form.completed }}
-

- - {% if task.note %} -
Note: {{ task.note|safe|urlize|linebreaks }}
- {% endif %} - -
-

Edit Task

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Title:{{ form.title }}
List:{{ form.list }}
Due:{{ form.due_date }}
Assigned to:{{ form.assigned_to }}
Note:{{ form.note }}
Priority:{{ form.priority }}
-

-
- -
- -

Add comment

- -

- -
- -

Comments on this task

- -
- {% for comment in comment_list %} -

- {{ comment.author.first_name }} {{ comment.author.last_name }}, - {{ comment.date|date:"F d Y P" }} - -

- {{ comment.body|safe|urlize|linebreaks }} - {% empty %} -

No Comments

- {% endfor %} -
- - {% endif %} - -{% endblock %} diff --git a/todo/urls.py b/todo/urls.py deleted file mode 100644 index ff52363..0000000 --- a/todo/urls.py +++ /dev/null @@ -1,23 +0,0 @@ -from django.conf.urls import url -from todo import views - -urlpatterns = [ - url(r'^$', views.list_lists, name="todo-lists"), - url(r'^mine/$', views.view_list, {'list_slug': 'mine'}, name="todo-mine"), - url(r'^(?P\d{1,4})/(?P[\w-]+)/delete$', views.del_list, name="todo-del_list"), - url(r'^task/(?P\d{1,6})$', views.view_task, name='todo-task_detail'), - url(r'^(?P\d{1,4})/(?P[\w-]+)$', views.view_list, name='todo-incomplete_tasks'), - url(r'^(?P\d{1,4})/(?P[\w-]+)/completed$', views.view_list, {'view_completed': True}, - name='todo-completed_tasks'), - url(r'^add_list/$', views.add_list, name="todo-add_list"), - url(r'^search-post/$', views.search_post, name="todo-search-post"), - url(r'^search/$', views.search, name="todo-search"), - - # View reorder_tasks is only called by JQuery for drag/drop task ordering - url(r'^reorder_tasks/$', views.reorder_tasks, name="todo-reorder_tasks"), - - url(r'^ticket/add/$', views.external_add, name="todo-external-add"), - url(r'^recent/added/$', views.view_list, {'list_slug': 'recent-add'}, name="todo-recently_added"), - url(r'^recent/completed/$', views.view_list, {'list_slug': 'recent-complete'}, - name="todo-recently_completed"), -] diff --git a/todo/utils.py b/todo/utils.py deleted file mode 100644 index cb6db3a..0000000 --- a/todo/utils.py +++ /dev/null @@ -1,51 +0,0 @@ -import datetime -from django.contrib import messages -from django.template.loader import render_to_string -from django.contrib.sites.models import Site -from django.core.mail import send_mail - -from todo.models import Item - -# Need for links in email templates -current_site = Site.objects.get_current() - - -def mark_done(request, done_items): - # Check for items in the mark_done POST array. If present, change status to complete. - for item in done_items: - i = Item.objects.get(id=item) - i.completed = True - i.completed_date = datetime.datetime.now() - i.save() - messages.success(request, "Item \"{i}\" marked complete.".format(i=i.title)) - - -def undo_completed_task(request, undone_items): - # Undo: Set completed items back to incomplete - for item in undone_items: - i = Item.objects.get(id=item) - i.completed = False - i.save() - messages.success(request, "Previously completed task \"{i}\" marked incomplete.".format(i=i.title)) - - -def del_tasks(request, deleted_items): - # Delete selected items - for item_id in deleted_items: - i = Item.objects.get(id=item_id) - messages.success(request, "Item \"{i}\" deleted.".format(i=i.title)) - i.delete() - - -def send_notify_mail(request, new_task): - # Send email - email_subject = render_to_string("todo/email/assigned_subject.txt", {'task': new_task}) - email_body = render_to_string( - "todo/email/assigned_body.txt", - {'task': new_task, 'site': current_site, }) - try: - send_mail( - email_subject, email_body, new_task.created_by.email, - [new_task.assigned_to.email], fail_silently=False) - except: - messages.error(request, "Task saved but mail not sent. Contact your administrator.") diff --git a/todo/views.py b/todo/views.py deleted file mode 100644 index c408ad8..0000000 --- a/todo/views.py +++ /dev/null @@ -1,351 +0,0 @@ -import datetime - -from django.contrib.auth.decorators import user_passes_test, login_required -from django.contrib.auth.models import User -from django.contrib import messages -from django.core.mail import send_mail -from django.core.urlresolvers import reverse -from django.db import IntegrityError -from django.db.models import Q -from django.http import HttpResponseRedirect, HttpResponse -from django.shortcuts import get_object_or_404, render -from django.template import RequestContext -from django.template.loader import render_to_string -from django.views.decorators.csrf import csrf_exempt -from django.contrib.sites.models import Site - -from todo import settings -from todo.forms import AddListForm, AddItemForm, EditItemForm, AddExternalItemForm, SearchForm -from todo.models import Item, List, Comment -from todo.utils import mark_done, undo_completed_task, del_tasks, send_notify_mail - -# Need for links in email templates -current_site = Site.objects.get_current() - - -def check_user_allowed(user): - """ - Conditions for user_passes_test decorator. - """ - if settings.STAFF_ONLY: - return user.is_authenticated() and user.is_staff - else: - return user.is_authenticated() - - -@user_passes_test(check_user_allowed) -def list_lists(request): - """ - Homepage view - list of lists a user can view, and ability to add a list. - """ - thedate = datetime.datetime.now() - searchform = SearchForm(auto_id=False) - - # Make sure user belongs to at least one group. - if request.user.groups.all().count() == 0: - messages.error(request, "You do not yet belong to any groups. Ask your administrator to add you to one.") - - # Superusers see all lists - if request.user.is_superuser: - list_list = List.objects.all().order_by('group', 'name') - else: - list_list = List.objects.filter(group__in=request.user.groups.all()).order_by('group', 'name') - - list_count = list_list.count() - - # superusers see all lists, so count shouldn't filter by just lists the admin belongs to - if request.user.is_superuser: - item_count = Item.objects.filter(completed=0).count() - else: - item_count = Item.objects.filter(completed=0).filter(list__group__in=request.user.groups.all()).count() - - return render(request, 'todo/list_lists.html', locals()) - - -@user_passes_test(check_user_allowed) -def del_list(request, list_id, list_slug): - """ - Delete an entire list. Danger Will Robinson! Only staff members should be allowed to access this view. - """ - list = get_object_or_404(List, slug=list_slug) - - if request.method == 'POST': - List.objects.get(id=list.id).delete() - messages.success(request, "{list_name} is gone.".format(list_name=list.name)) - return HttpResponseRedirect(reverse('todo-lists')) - else: - item_count_done = Item.objects.filter(list=list.id, completed=1).count() - item_count_undone = Item.objects.filter(list=list.id, completed=0).count() - item_count_total = Item.objects.filter(list=list.id).count() - - return render(request, 'todo/del_list.html', locals()) - - -@user_passes_test(check_user_allowed) -def view_list(request, list_id=0, list_slug=None, view_completed=False): - """ - Display and manage items in a list. - """ - - # Make sure the accessing user has permission to view this list. - # Always authorize the "mine" view. Admins can view/edit all lists. - if list_slug == "mine" or list_slug == "recent-add" or list_slug == "recent-complete": - auth_ok = True - else: - list = get_object_or_404(List, id=list_id) - if list.group in request.user.groups.all() or request.user.is_staff or list_slug == "mine": - auth_ok = True - else: # User does not belong to the group this list is attached to - messages.error(request, "You do not have permission to view/edit this list.") - - # Process all possible list interactions on each submit - mark_done(request, request.POST.getlist('mark_done')) - del_tasks(request, request.POST.getlist('del_tasks')) - undo_completed_task(request, request.POST.getlist('undo_completed_task')) - - thedate = datetime.datetime.now() - created_date = "%s-%s-%s" % (thedate.year, thedate.month, thedate.day) - - # Get set of items with this list ID, or filter on items assigned to me, or recently added/completed - if list_slug == "mine": - task_list = Item.objects.filter(assigned_to=request.user, completed=False) - completed_list = Item.objects.filter(assigned_to=request.user, completed=True) - - elif list_slug == "recent-add": - # Only show items in lists that are in groups that the current user is also in. - # Assume this only includes uncompleted items. - task_list = Item.objects.filter( - list__group__in=(request.user.groups.all()), - completed=False).order_by('-created_date')[:50] - - elif list_slug == "recent-complete": - # Only show items in lists that are in groups that the current user is also in. - task_list = Item.objects.filter( - list__group__in=request.user.groups.all(), - completed=True).order_by('-completed_date')[:50] - - else: - task_list = Item.objects.filter(list=list.id, completed=0) - completed_list = Item.objects.filter(list=list.id, completed=1) - - if request.POST.getlist('add_task'): - form = AddItemForm(list, request.POST, initial={ - 'assigned_to': request.user.id, - 'priority': 999, - }) - - if form.is_valid(): - new_task = form.save() - - # Send email alert only if Notify checkbox is checked AND assignee is not same as the submitter - if "notify" in request.POST and new_task.assigned_to != request.user: - send_notify_mail(request, new_task) - - messages.success(request, "New task \"{t}\" has been added.".format(t=new_task.title)) - return HttpResponseRedirect(request.path) - else: - # Don't allow adding new tasks on some views - if list_slug != "mine" and list_slug != "recent-add" and list_slug != "recent-complete": - form = AddItemForm(list, initial={ - 'assigned_to': request.user.id, - 'priority': 999, - }) - - return render(request, 'todo/view_list.html', locals()) - - -@user_passes_test(check_user_allowed) -def view_task(request, task_id): - """ - View task details. Allow task details to be edited. - """ - task = get_object_or_404(Item, pk=task_id) - comment_list = Comment.objects.filter(task=task_id) - - # Ensure user has permission to view item. - # Get the group this task belongs to, and check whether current user is a member of that group. - # Admins can edit all tasks. - - if task.list.group in request.user.groups.all() or request.user.is_staff: - auth_ok = True - - if request.POST: - form = EditItemForm(request.POST, instance=task) - - if form.is_valid(): - form.save() - - # Also save submitted comment, if non-empty - if request.POST['comment-body']: - c = Comment( - author=request.user, - task=task, - body=request.POST['comment-body'], - ) - c.save() - - # And email comment to all people who have participated in this thread. - email_subject = render_to_string("todo/email/assigned_subject.txt", {'task': task}) - email_body = render_to_string( - "todo/email/newcomment_body.txt", - {'task': task, 'body': request.POST['comment-body'], 'site': current_site, 'user': request.user} - ) - - # Get list of all thread participants - task creator plus everyone who has commented on it. - recip_list = [] - recip_list.append(task.created_by.email) - commenters = Comment.objects.filter(task=task) - for c in commenters: - recip_list.append(c.author.email) - recip_list = set(recip_list) # Eliminate duplicates - - try: - send_mail(email_subject, email_body, task.created_by.email, recip_list, fail_silently=False) - messages.success(request, "Comment sent to thread participants.") - except: - messages.error(request, "Comment saved but mail not sent. Contact your administrator.") - - messages.success(request, "The task has been edited.") - - return HttpResponseRedirect(reverse('todo-incomplete_tasks', args=[task.list.id, task.list.slug])) - else: - form = EditItemForm(instance=task) - if task.due_date: - thedate = task.due_date - else: - thedate = datetime.datetime.now() - else: - messages.info(request, "You do not have permission to view/edit this task.") - - return render(request, 'todo/view_task.html', locals()) - - -@csrf_exempt -@user_passes_test(check_user_allowed) -def reorder_tasks(request): - """ - Handle task re-ordering (priorities) from JQuery drag/drop in view_list.html - """ - newtasklist = request.POST.getlist('tasktable[]') - # First item in received list is always empty - remove it - del newtasklist[0] - - # Re-prioritize each item in list - i = 1 - for t in newtasklist: - newitem = Item.objects.get(pk=t) - newitem.priority = i - newitem.save() - i += 1 - - # All views must return an httpresponse of some kind ... without this we get - # error 500s in the log even though things look peachy in the browser. - return HttpResponse(status=201) - - -@login_required -def external_add(request): - """ - Allow users who don't have access to the rest of the ticket system to file a ticket in a specific list. - Public tickets are unassigned unless settings.DEFAULT_ASSIGNEE exists. - """ - if request.POST: - form = AddExternalItemForm(request.POST) - - if form.is_valid(): - item = form.save(commit=False) - item.list_id = settings.DEFAULT_LIST_ID - item.created_by = request.user - if settings.DEFAULT_ASSIGNEE: - item.assigned_to = User.objects.get(username=settings.DEFAULT_ASSIGNEE) - item.save() - - email_subject = render_to_string("todo/email/assigned_subject.txt", {'task': item.title}) - email_body = render_to_string("todo/email/assigned_body.txt", {'task': item, 'site': current_site, }) - try: - send_mail( - email_subject, email_body, item.created_by.email, [item.assigned_to.email, ], fail_silently=False) - except: - messages.error(request, "Task saved but mail not sent. Contact your administrator.") - - messages.success(request, "Your trouble ticket has been submitted. We'll get back to you soon.") - - return HttpResponseRedirect(settings.PUBLIC_SUBMIT_REDIRECT) - else: - form = AddExternalItemForm() - - return render(request, 'todo/add_external_task.html', locals()) - - -@user_passes_test(check_user_allowed) -def add_list(request): - """ - Allow users to add a new todo list to the group they're in. - """ - if request.POST: - form = AddListForm(request.user, request.POST) - if form.is_valid(): - try: - form.save() - messages.success(request, "A new list has been added.") - return HttpResponseRedirect(request.path) - except IntegrityError: - messages.error( - request, - "There was a problem saving the new list. " - "Most likely a list with the same name in the same group already exists.") - else: - if request.user.groups.all().count() == 1: - form = AddListForm(request.user, initial={"group": request.user.groups.all()[0]}) - else: - form = AddListForm(request.user) - - return render(request, 'todo/add_list.html', locals()) - - -@user_passes_test(check_user_allowed) -def search_post(request): - """ - Redirect POST'd search param to query GET string - """ - if request.POST: - q = request.POST.get('q') - url = reverse('todo-search') + "?q=" + q - return HttpResponseRedirect(url) - - -@user_passes_test(check_user_allowed) -def search(request): - """ - Search for tasks - """ - if request.GET: - - query_string = '' - found_items = None - if ('q' in request.GET) and request.GET['q'].strip(): - query_string = request.GET['q'] - - found_items = Item.objects.filter( - Q(title__icontains=query_string) | - Q(note__icontains=query_string) - ) - else: - - # What if they selected the "completed" toggle but didn't type in a query string? - # We still need found_items in a queryset so it can be "excluded" below. - found_items = Item.objects.all() - - if 'inc_complete' in request.GET: - found_items = found_items.exclude(completed=True) - - else: - query_string = None - found_items = None - - return render( - request, - 'todo/search_results.html', { - 'query_string': query_string, - 'found_items': found_items - }, context_instance=RequestContext(request)) diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 090c1fa..0000000 --- a/tox.ini +++ /dev/null @@ -1,23 +0,0 @@ -[tox] -envlist = - flake8py{2,3} - -[testenv] -# deps = pytest -commands = - # py.test - -[testenv:flake8py2] -basepython = python2.7 -deps = flake8 -commands = flake8 . - -[testenv:flake8py3] -basepython = python3.5 -deps = flake8 -commands = flake8 . - -[flake8] -max-line-length = 120 -max-complexity = 10 -exclude = [build, lib, bin, dist, docs/conf.py, */migrations, .eggs, *.egg-info] diff --git a/werf.yml b/werf.yml new file mode 100644 index 0000000..f8622f4 --- /dev/null +++ b/werf.yml @@ -0,0 +1,5 @@ +project: coins-demo +configVersion: 1 +--- +image: ~ +dockerfile: Dockerfile