Skip to content

Commit

Permalink
Merge branch 'release/v1.7.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
pbellon committed Sep 9, 2014
2 parents e6369f2 + 89b523c commit 10a5a41
Show file tree
Hide file tree
Showing 141 changed files with 4,425 additions and 748 deletions.
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ Thumbs.db
fabfile.py
# Users media
app/media/*

# Topic Skeleton images exception rules
!app/media/topics-skeletons/body-count.jpg
!app/media/topics-skeletons/corporate-nets.jpg
!app/media/topics-skeletons/family-affairs-schema.jpg
!app/media/topics-skeletons/family-affairs.jpg
!app/media/topics-skeletons/political-influence.jpg
!app/media/topics-skeletons/supply-chain.jpg
# Binary files and database
*.db

Expand Down
30 changes: 16 additions & 14 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
language: python
python:
- "2.7"
- '2.7'
notifications:
irc:
channels:
- "anchor.foonetic.net#jplusplus"
- anchor.foonetic.net#jplusplus
on_success: always
on_failure: always
slack:
secure: lc2NcRnOWDXmxoQt8wJh0947g9+cIpDaSe2SAJlXigdPL8qzq04GriV0KmRlcVBvjqB4bViWfEsn5ldtGBBiu5wyQ/nIckESfV/8zpLmUM5uEqhx1U3f9YJLDLjH1THNAJBIL9EI9DKo9IHF2uiZF0/sGj4ejorjiZC8J+K3MWk=
before_install:
- export COVERALLS_REPO_TOKEN=6opRu71NU2ODTYXULbiEUS1EPTcDNcZqI
- export COVERALLS_SERVICE_NAME=travis-ci
- export DATABASE_URL=sqlite:///test.db
- export DJANGO_SETTINGS_MODULE=app.settings_tests
- export NEO4J_PORT=7474
- export NEO4J_VERSION=1.9.1
- export COVERALLS_REPO_TOKEN=UDtLCy1He2SylMvJMjq1Uu9f1zVFbTim8
- export COVERALLS_SERVICE_NAME=travis-ci
- export DATABASE_URL=sqlite:///test.db
- export DJANGO_SETTINGS_MODULE=app.settings_tests
- export NEO4J_PORT=7474
- export NEO4J_VERSION=1.9.1
install:
- ./install_local_neo4j.bash $NEO4J_VERSION
- ./lib/neo4j/bin/neo4j start
- pip install -r test_requirements.txt
- pip install coveralls
- ./install_local_neo4j.bash $NEO4J_VERSION
- ./lib/neo4j/bin/neo4j start
- pip install -r test_requirements.txt
- pip install coveralls
script:
- coverage run --source=app.detective ./manage.py test detective --pythonpath=. --traceback
- coverage run --source=app.detective ./manage.py test detective --pythonpath=. --traceback
after_success:
- coveralls
- coveralls
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ test:
make startdb
./manage.py syncdb -v 0 --noinput --traceback --pythonpath=. --settings=app.settings_tests
# Launch test with coverage
python -W ignore::DeprecationWarning $(COVERAGE) run --source=app.detective ./manage.py test detective --pythonpath=. --settings=app.settings_tests --traceback
-python -W ignore::DeprecationWarning $(COVERAGE) run --source=app.detective ./manage.py test detective --pythonpath=. --settings=app.settings_tests --traceback
# Send report to coveralls
coveralls
# Stop database in order to restore it
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[License](https://github.com/jplusplus/detective.io/blob/master/LICENSE)
[Test coverage](https://coveralls.io/r/jplusplus/detective.io)
[Documentation](http://docs.detective.io/en/latest/)
*Version 1.6.5 Seestern*
*Version 1.7.0 Seepferd*

## Installation

Expand Down
29 changes: 28 additions & 1 deletion app/detective/admin.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
from app.detective import utils
from app.detective.models import QuoteRequest, Topic, TopicToken, SearchTerm, Article, DetectiveProfileUser
from app.detective.models import QuoteRequest
from app.detective.models import Topic
from app.detective.models import TopicSkeleton
from app.detective.models import TopicToken
from app.detective.models import SearchTerm
from app.detective.models import Article
from app.detective.models import DetectiveProfileUser
from app.detective.models import Subscription
from app.detective.models import PLANS_CHOICES
from django.conf import settings
from django.contrib import admin
from django import forms
from django.db import models
from django.db.models import CharField
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
Expand Down Expand Up @@ -130,6 +140,17 @@ def get_form(self, request, obj=None, **kwargs):

admin.site.register(Topic, TopicAdmin)


class TopicSkeletonForm(forms.ModelForm):
target_plans = forms.MultipleChoiceField(choices=PLANS_CHOICES)


class TopicSkeletonAdmin(admin.ModelAdmin):
form = TopicSkeletonForm
list_display = ("title","picture", "picture_credits","ontology", "target_plans")

admin.site.register(TopicSkeleton, TopicSkeletonAdmin)

class ArticleAdmin(admin.ModelAdmin):
save_on_top = True
prepopulated_fields = {'slug': ('title',)}
Expand All @@ -142,6 +163,12 @@ class DetectiveProfileUserInline(admin.StackedInline):
can_delete = False
verbose_name_plural = 'detective settings'

class SubscriptionAdmin(admin.ModelAdmin):
list_display = ("user", "email", "plan", "type", "name", "status")
list_filter = ("status", )

admin.site.register(Subscription, SubscriptionAdmin)

# Define a new User admin
class UserAdmin(UserAdmin):
inlines = (DetectiveProfileUserInline, )
Expand Down
2 changes: 2 additions & 0 deletions app/detective/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class UnavailableImage(Exception): pass
class NotAnImage(Exception): pass
67 changes: 67 additions & 0 deletions app/detective/fixtures/default_skeletons.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion app/detective/fixtures/default_topics.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"first_name": "detective",
"last_name": "detective",
"username": "detective",
"password": ""
"password": "",
"is_superuser" : true
}
},
{
Expand Down
2 changes: 1 addition & 1 deletion app/detective/fixtures/tests_topics.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"ontology_as_owl": null,
"ontology_as_mod": "testtopic",
"about": "",
"background": "",
"background": "topics-skeletons/supply-chain.jpg",
"public": true,
"featured": true
}
Expand Down
50 changes: 36 additions & 14 deletions app/detective/individual.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ def read_list(self, object_list, bundle):
def create_detail(self, object_list, bundle):
if not self.check_contribution_permission(object_list, bundle, 'add'):
raise Unauthorized("Sorry, only staff or contributors can create resource.")
# check if user can add regarding to his plan
topic = get_topic_from_request(bundle.request)
owner_profile = topic.author.detectiveprofileuser
if owner_profile.nodes_max() > -1 and owner_profile.nodes_count()[topic.slug] >= owner_profile.nodes_max():
raise Unauthorized("Sorry, you have to upgrade your plan.")
return True

def update_detail(self, object_list, bundle):
Expand Down Expand Up @@ -489,7 +494,6 @@ def arr_no_dict_dup(in_arr):

def get_patch(self, request, **kwargs):
self.method_check(request, allowed=['post'])
#self.is_authenticated(request)
self.throttle_check(request)
self.is_authenticated(request)
bundle = self.build_bundle(request=request)
Expand All @@ -498,7 +502,7 @@ def get_patch(self, request, **kwargs):
try:
node = model.objects.get(id=kwargs["pk"])
except ObjectDoesNotExist:
raise Http404("Sorry, unkown node.")
raise Http404("Sorry, unknown node.")
# Parse only body string
body = json.loads(request.body) if type(request.body) is str else request.body
# Copy data to allow dictionary resizing
Expand All @@ -515,29 +519,48 @@ def get_patch(self, request, **kwargs):
attr = getattr(node, field)
# It's a relationship
if hasattr(attr, "_rel"):
existing_rels_id = [r.id for r in attr.all()]
rules = request.current_topic.get_rules()
# Model that manages properties
though = rules.model( self.get_model() ).field(field).get("through")
related_model = attr._rel.relationship.target_model
# Clean the field to avoid duplicates
attr.clear()
# Load the json-formated relationships
data[field] = rels = value
# For each relation...
# For each relationship...
for idx, rel in enumerate(rels):
if type(rel) in [str, int]: rel = dict(id=rel)
if type(rel) in [str, int]:
rel = dict(id=rel)
# We receied an object with an id
if rel.has_key("id"):
# skip for existing relationships
if rel["id"] in existing_rels_id:
continue
# Get the related object
try:
related = related_model.objects.get(id=rel["id"])
# Creates the relationship between the two objects
attr.add(related)
except ObjectDoesNotExist:
del data[field][idx]
# Too bad! Go to the next related object
continue
else:
attr.add(related)
# removing unused relationship
rel_type = self.get_model_field(field).rel_type
for relationship in node.node.relationships.all(types=[rel_type]):
if relationship.end.id not in [rel["id"] for rel in rels]:
relation_id = relationship.id
relationship.delete()
try:
property = though.objects.get(_relationship=relation_id)
except ObjectDoesNotExist:
pass
else:
property.delete()

# It's a literal value and not the ID
elif field != 'id' and value is not None:
elif field != 'id':
field_prop = self.get_model_field(field)._property
if isinstance(field_prop, DateProperty):
if isinstance(field_prop, DateProperty) and value != None:
try:
# It's a date and therefor `value` should be converted as it
value = datetime.strptime(value, RFC_DATETIME_FORMAT)
Expand Down Expand Up @@ -570,15 +593,14 @@ def get_patch(self, request, **kwargs):

def get_relationships(self, request, **kwargs):
# Extract node id from given node uri
node_id = lambda uri: re.search(r'(\d+)$', uri).group(1)
def node_id(uri) : return re.search(r'(\d+)$', uri).group(1)
# Get the end of the given relationship
rel_from = lambda rel, side: node_id(rel.__dict__["_dic"][side])
connected = lambda rel, idx: rel_from(rel, "end") == idx or rel_from(rel, "start") == idx
def rel_from(rel, side): return node_id(rel.__dict__["_dic"][side])
def connected(rel, idx): return rel_from(rel, "end") == idx or rel_from(rel, "start") == idx

self.method_check(request, allowed=['get'])
self.throttle_check(request)
pk = kwargs['pk']

node = connection.nodes.get(pk)
# Only the relationships for a given field
if "field" in kwargs:
Expand Down
62 changes: 62 additions & 0 deletions app/detective/management/commands/orphans.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env python
# Encoding: utf-8
# -----------------------------------------------------------------------------
# Project : Detective.io
# -----------------------------------------------------------------------------
# Author : Edouard Richard <[email protected]>
# -----------------------------------------------------------------------------
# License : GNU GENERAL PUBLIC LICENSE v3
# -----------------------------------------------------------------------------
# Creation : 04-09-2014
# Last mod : 04-09-2014
# -----------------------------------------------------------------------------
from django.core.management.base import BaseCommand, CommandError
import json
from django.db.models.loading import get_model
from optparse import make_option
from app.detective.models import Topic
from app.detective import utils

class Command(BaseCommand):
help = "Detect orphans in the graph."
option_list = BaseCommand.option_list + (
make_option('--fix',
action='store_true',
dest='fix',
default=False,
help='Delete orphans'),
)

def handle(self, *args, **options):
total_orphans_count = 0
for topic in Topic.objects.all():
# escape common & energy as usual
if topic.slug in ["common", "energy"]: continue
self.stdout.write("Topic: %s" % (topic))
orphans_count = 0
try:
for Model in topic.get_models():
try:
fields = utils.get_model_fields(Model)
for field in fields:
if field["related_model"] and field["direction"] == "out" and "through" in field["rules"]:
ids= []
for Model in Model.objects.all():
ids.extend([_.id for _ in Model.node.relationships.all()])
Properties = field["rules"]["through"]
for info in Properties.objects.all():
if info._relationship not in ids:
self.stdout.write("\t%s is an orphelin property of the model %s. The relation doesn't exist no more." % (info._NodeModel__node, Model.__class__.__name__))
orphans_count += 1
total_orphans_count += 1
if options["fix"]:
self.stdout.write("\tremoving %s" % (info))
info.delete()
except Exception as e:
self.stderr.write("\tError with model %s (%s)" % (Model.__class__.__name__, e))
self.stdout.write("\tfound %d orphans" % (orphans_count))
except Exception as e:
self.stderr.write("\tError with model %s (%s)" % (Model.__class__.__name__, e))
self.stdout.write("TOTAL: found %d orphans" % (total_orphans_count))

# EOF
47 changes: 47 additions & 0 deletions app/detective/management/commands/plans.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/usr/bin/env python
# Encoding: utf-8
# -----------------------------------------------------------------------------
# Project : Detective.io
# -----------------------------------------------------------------------------
# Author : Edouard Richard <[email protected]>
# -----------------------------------------------------------------------------
# License : GNU GENERAL PUBLIC LICENSE v3
# -----------------------------------------------------------------------------
# Creation : 05-09-2014
# Last mod : 05-09-2014
# -----------------------------------------------------------------------------
from django.core.management.base import BaseCommand
from optparse import make_option
from django.contrib.auth.models import User
from app.detective.models import DetectiveProfileUser, Topic

class Command(BaseCommand):
help = "Detect orphans in the graph."
option_list = BaseCommand.option_list + (
make_option('--fix',
action='store_true',
dest='fix',
default=False,
help='upgrade the plan'),
)

def handle(self, *args, **options):
from django.conf import settings
for user in User.objects.all():
profile = DetectiveProfileUser.objects.get(user=user)
user_topics = Topic.objects.filter(author=user)
number_of_topic = user_topics.count()
max_number_of_entities = 0
for topic in user_topics:
max_number_of_entities = max(max_number_of_entities, topic.entities_count())
for plan in settings.PLANS:
best_plan, proprs = plan.items()[0]
if proprs.get("max_investigation") != -1 and number_of_topic > proprs.get("max_investigation"): continue
if proprs.get("max_entities") != -1 and max_number_of_entities > proprs.get("max_entities") : continue
break
self.stdout.write("%s : %s ---> %s" % (user.username, profile.plan, best_plan))
if options["fix"]:
profile.plan = best_plan
profile.save()

# EOF
Loading

0 comments on commit 10a5a41

Please sign in to comment.