Initial import

This commit is contained in:
2010-02-23 10:16:25 +00:00
committed by dreddit
commit b8e5647148
26 changed files with 957 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.pyc

0
__init__.py Normal file
View File

0
eve_api/__init__.py Normal file
View File

32
eve_api/admin.py Normal file
View File

@@ -0,0 +1,32 @@
"""
Admin interface models. Automatically detected by admin.autodiscover().
"""
from django.contrib import admin
from eve_api.models import *
class EVEAccountAdmin(admin.ModelAdmin):
list_display = ('id', 'user')
search_fields = ['id']
admin.site.register(EVEAccount, EVEAccountAdmin)
class EVEPlayerCharacterAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'corporation')
search_fields = ['id', 'name']
admin.site.register(EVEPlayerCharacter, EVEPlayerCharacterAdmin)
class EVEPlayerCorporationInline(admin.TabularInline):
model = EVEPlayerCorporation
fields = ('name', 'ticker')
extra = 0
class EVEPlayerAllianceAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'ticker', 'member_count', 'date_founded')
search_fields = ['name', 'ticker']
date_hierarchy = 'date_founded'
inlines = [EVEPlayerCorporationInline]
admin.site.register(EVEPlayerAlliance, EVEPlayerAllianceAdmin)
class EVEPlayerCorporationAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'ticker', 'member_count', 'alliance')
search_fields = ['name', 'ticker']
admin.site.register(EVEPlayerCorporation, EVEPlayerCorporationAdmin)

16
eve_api/api_exceptions.py Normal file
View File

@@ -0,0 +1,16 @@
"""
Contains exeptions used in the eve_api app.
"""
class APIAuthException(Exception):
"""
Raised when an invalid userID and/or authKey were provided.
"""
def __str__(self):
return "An authentication was encountered while querying the EVE API."
class APINoUserIDException(Exception):
"""
Raised when a userID is required, but missing.
"""
def __str__(self):
return "This query requires a valid userID, but yours is either missing or invalid."

View File

72
eve_api/api_puller/accounts.py Executable file
View File

@@ -0,0 +1,72 @@
#!/usr/bin/env python
"""
This module abstracts the pulling of account data from the EVE API.
"""
from xml.dom import minidom
from datetime import datetime
if __name__ == "__main__":
# Only mess with the environmental stuff if this is being ran directly.
from importer_path import fix_environment
fix_environment()
from django.conf import settings
from eve_proxy.models import CachedDocument
from eve_api.api_exceptions import APIAuthException, APINoUserIDException
from eve_api.models import EVEAccount, EVEPlayerCharacter, EVEPlayerCorporation
def import_eve_account(api_key, user_id):
"""
Imports an account from the API into the EVEAccount model.
"""
print user_id, ":", api_key
auth_params = {'userID': user_id, 'apiKey': api_key}
account_doc = CachedDocument.objects.api_query('/account/Characters.xml.aspx',
params=auth_params,
no_cache=False)
#print account_doc.body
dom = minidom.parseString(account_doc.body)
characters_node_children = dom.getElementsByTagName('rowset')[0].childNodes
# Create or retrieve the account last to make sure everything
# before here is good to go.
try:
account = EVEAccount.objects.get(id=user_id)
except EVEAccount.DoesNotExist:
account = EVEAccount(id=user_id)
account.api_key = api_key
account.api_user_id = user_id
account.api_last_updated = datetime.now()
account.save()
for node in characters_node_children:
try:
# Get this first, as it's safe.
corporation_id = node.getAttribute('corporationID')
corp, created = EVEPlayerCorporation.objects.get_or_create(id=corporation_id)
# Do this last, since the things we retrieved above are used
# on the EVEPlayerCharacter object's fields.
character_id = node.getAttribute('characterID')
pchar, created = EVEPlayerCharacter.objects.get_or_create(id=character_id)
name = node.getAttribute('name')
# Save these for last to keep the save count low.
pchar.name = name
pchar.corporation = corp
pchar.save()
account.characters.add(pchar)
except AttributeError:
# This must be a Text node, ignore it.
continue
return account
if __name__ == "__main__":
"""
Test import.
"""
api_key = settings.EVE_API_USER_KEY
#api_key += "1"
user_id = settings.EVE_API_USER_ID
import_eve_account(api_key, user_id)

130
eve_api/api_puller/alliances.py Executable file
View File

@@ -0,0 +1,130 @@
#!/usr/bin/env python
"""
This module pulls the master alliance XML list from the API and dumps it in the
api_puller/xml_cache directory as needed. All alliance data must be updated
in bulk, which is done reasonably quickly.
"""
from xml.dom import minidom
import os
import sys
from datetime import datetime
if __name__ == "__main__":
# Only mess with the environmental stuff if this is being ran directly.
from importer_path import fix_environment
fix_environment()
from django.conf import settings
from eve_api.models import EVEPlayerAlliance, EVEPlayerCorporation
from eve_proxy.models import CachedDocument
# This stores a list of all corps whose alliance attribute has been updated.
UPDATED_CORPS = []
def __update_corp_from_alliance_node(alliance_node, alliance):
"""
Updates a corp's alliance membership from an alliance <row> element.
"""
member_corp_nodelist = alliance_node.getElementsByTagName('rowset')[0].childNodes
for node in member_corp_nodelist:
corp_row_node = None
try:
# If this fails, this is a Text node and should be ignored.
corporation_id = int(node.getAttribute('corporationID'))
except AttributeError:
# This is probably a Text node, ignore it.
continue
corp, created = EVEPlayerCorporation.objects.get_or_create(id=corporation_id)
corp.id = corporation_id
corp.alliance = alliance
corp.alliance_join_date = datetime.strptime(alliance_node.getAttribute('startDate'),
'%Y-%m-%d %H:%M:%S')
corp.save()
# Store the corp in the updated corps list for later checks.
UPDATED_CORPS.append(corp.id)
def __remove_invalid_corp_alliance_memberships():
"""
Compares UPDATED_CORPS list to the full list of player corporations. If
the corporation was not updated from being found in one of the alliance
data sets, it has no alliance affiliation and needs to be set to no
alliance if it is not already a None value.
"""
all_corps = EVEPlayerCorporation.objects.all()
# This is not terribly efficient, but it will do for a background process.
for corp in all_corps:
"""
If the corp is not in the UPDATED_CORP list that was built from
alliance memberCorporations rowsets, then it does not belong to an
alliance and should be un-allianced if it currently is.
"""
if corp.id not in UPDATED_CORPS and corp.alliance != None:
corp.alliance = None
corp.save()
def __start_full_import():
"""
This method runs a full import of all known alliances. This may take a few
minutes and should be ran regularly if you are maintaining a full corp
list of all EVE corps as well.
"""
print "Querying /eve/AllianceList.xml.aspx/"
alliance_doc = CachedDocument.objects.api_query('/eve/AllianceList.xml.aspx')
print "Parsing..."
dom = minidom.parseString(alliance_doc.body)
result_node_children = dom.getElementsByTagName('result')[0].childNodes
# This will hold a reference to the <rowset name="alliances> Element.
alliances_rowset_node = None
# For some odd reason, two text nodes and an Element are children of
# the result Element. Find the alliances rowset from its children.
for node in result_node_children:
try:
# The node we want has a 'name' attribute.
if node.getAttribute('name') == 'alliances':
# Store the reference for later use.
alliances_rowset_node = node
# Look no further.
break
except AttributeError:
# This must be a Text node, ignore it.
continue
if alliances_rowset_node == None:
print "No alliance rowset node could be found. Your AllianceList.xml file may be corrupt."
sys.exit(1)
# We now have a list of <row> tags representing each alliance.
print "Updating alliance and member corporation data..."
for alliance_node in alliances_rowset_node.childNodes:
try:
# If this fails, this is a Text node and should be ignored.
alliance_id = int(alliance_node.getAttribute('allianceID'))
except AttributeError:
# This is probably a Text node, ignore it.
continue
"""
Search for an existing EVEPlayerAlliance object with the given
alliance ID. Create one if it doesn't exist, retrieve the existing
object if it's already there.
"""
alliance, created = EVEPlayerAlliance.objects.get_or_create(id=alliance_id)
alliance.id = alliance_id
alliance.name = alliance_node.getAttribute('name')
alliance.ticker = alliance_node.getAttribute('shortName')
alliance.member_count = alliance_node.getAttribute('memberCount')
alliance.date_founded = datetime.strptime(alliance_node.getAttribute('startDate'),
'%Y-%m-%d %H:%M:%S')
alliance.save()
# Update member corp alliance attributes.
__update_corp_from_alliance_node(alliance_node, alliance)
print "Alliances and member corps updated."
print "Removing corps alliance memberships that are no longer valid..."
__remove_invalid_corp_alliance_memberships()
if __name__ == "__main__":
__start_full_import()

55
eve_api/api_puller/corps.py Executable file
View File

@@ -0,0 +1,55 @@
#!/usr/bin/env python
"""
Module for updating corp information. If this is ran directly, the module will
iterate through all known alliances, looking at the corps in each alliance's
member list. This can be very time-consuming and should not be done often.
Within your applications, you may call query_and_update_corp() to update
an individual corp object as need be.
NOTE: To get corp data, it must be a member of an alliance.
"""
if __name__ == "__main__":
# Only mess with the environmental stuff if this is being ran directly.
from importer_path import fix_environment
fix_environment()
from eve_api.models import EVEPlayerAlliance, EVEPlayerCorporation
def start_full_import():
"""
Imports all of the corps that are in all of the known alliances.
WARNING: THIS WILL TAKE A _LONG_ TIME AND MUST BE RAN AFTER
eve_db.api_puller.alliances.__start_full_import() OR YOU WON'T GET ALL
OF THE CORPS (or any at all).
"""
alliances = EVEPlayerAlliance.objects.all()
# These two variables are used to track progress.
alliance_count = alliances.count()
# Use this as a progress indicator.
current_alliance_num = 1
for alliance in alliances:
# Keep the user informed as to the progress.
print "Alliance %d of %d..." % (current_alliance_num, alliance_count)
# A list of the alliance's member corps.
member_corps = alliance.eveplayercorporation_set.all()
# We're getting the list of corps to update from alliance memberships.
for corp in member_corps:
print "Querying", corp.id
corp.query_and_update_corp()
# Increment progress counter.
current_alliance_num += 1
if __name__ == "__main__":
"""
If ran directly, this will grab all of the corps from the known alliances.
WARNING: THIS WILL TAKE A VERY LONG TIME TO RUN! IT IS SUGGESTED YOU ONLY
GRAB CORPS AS YOU NEED THEM.
"""
start_full_import()

View File

@@ -0,0 +1,16 @@
import os
import sys
# The path to the folder containing settings.py.
BASE_PATH = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
APPS_PATH = os.path.join(BASE_PATH, 'apps')
def fix_environment():
"""
Callable function to set up all of the Django environmental variables and
pathing for directly executable python modules.
"""
from importer_path import BASE_PATH
# Prepare the environment
sys.path.insert(0, APPS_PATH)
sys.path.insert(0, BASE_PATH)
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'

15
eve_api/app_defines.py Normal file
View File

@@ -0,0 +1,15 @@
"""
Standard definitions that don't change.
"""
# API status definitions for EVEAccount.
API_STATUS_PENDING = 0
API_STATUS_OK = 1
API_STATUS_AUTH_ERROR = 2
API_STATUS_OTHER_ERROR = 3
# This tuple is used to assemble the choices list for the field.
API_STATUS_CHOICES = (
(API_STATUS_PENDING, 'Unknown'),
(API_STATUS_OK, 'OK'),
(API_STATUS_AUTH_ERROR, 'Auth Error'),
(API_STATUS_OTHER_ERROR, 'Other Error'),
)

96
eve_api/managers.py Normal file
View File

@@ -0,0 +1,96 @@
from xml.dom import minidom
from django.db import models
from eve_proxy.models import CachedDocument
class InvalidCorpID(Exception):
"""
Thrown when an invalid corp id is given in an api query.
"""
def __init__(self, id):
self.value = "ID: %s does not match any corporation." % id
def __str___(self):
return repr(self.value)
def _api_get_id_from_name(name):
"""
Queries the EVE API looking for the ID of the specified corporation,
alliance, or character based on its name. This is not case sensitive.
name: (str) Corporation name to search for.
"""
query_doc = CachedDocument.objects.api_query('/eve/CharacterID.xml.aspx',
params={'names': name})
query_dat = query_doc.body.decode("utf-8", "replace")
dom = minidom.parseString(query_dat)
id_node = dom.getElementsByTagName('row')[0]
object_id = id_node.getAttribute('characterID')
if object_id == '0':
raise self.model.DoesNotExist('EVE API returned no matches for the provided corp name.')
else:
return int(object_id)
class EVEPlayerCharacterManager(models.Manager):
def api_get_id_from_name(self, name):
"""
This uses a common call for corps, characters, and alliances.
"""
return _api_get_id_from_name(name)
class EVEPlayerAllianceManager(models.Manager):
def api_get_id_from_name(self, name):
"""
This uses a common call for corps, characters, and alliances.
"""
return _api_get_id_from_name(name)
class EVEPlayerCorporationManager(models.Manager):
def get_or_query_by_id(self, corp_id):
"""
Queries for a corporation. If the corp can't be founded, check the
EVE API service for information on it. If a match still can't be
found, return EVEPlayerCorporation.DoesNotExist.
corp_id: (int) Corp's ID.
"""
try:
return self.get(id=corp_id)
except self.model.DoesNotExist:
try:
self.api_corp_sheet_xml(corp_id)
new_corp = self.create(id=corp_id)
new_corp.query_and_update_corp()
return new_corp
except InvalidCorpID:
raise
def api_get_id_from_name(self, name):
"""
This uses a common call for corps, characters, and alliances.
"""
return _api_get_id_from_name(name)
def api_corp_sheet_xml(self, id):
"""
Returns a corp's data sheet from the EVE API in the form of an XML
minidom doc.
"""
corp_doc = CachedDocument.objects.api_query('/corp/CorporationSheet.xml.aspx',
params={'corporationID': id})
corp_dat = corp_doc.body.decode("utf-8", "replace")
# Convert incoming data to UTF-8.
dom = minidom.parseString(corp_dat)
error_node = dom.getElementsByTagName('error')
# If there's an error, see if it's because the corp doesn't exist.
if error_node:
error_code = error_node[0].getAttribute('code')
if error_code == '523':
raise InvalidCorpID(id)
return dom

View File

@@ -0,0 +1,6 @@
"""
By importing all of these sub-modules, the models package is transparently
accessible by the rest of the project. This makes it act just as if it were
one monolithic models.py.
"""
from api_player import *

View File

@@ -0,0 +1,195 @@
"""
This module holds data from the EVE XML API.
"""
from django.db import models
from django.contrib.auth.models import User
from eve_proxy.models import CachedDocument
from eve_api.managers import EVEPlayerCorporationManager, EVEPlayerAllianceManager, EVEPlayerCharacterManager
from eve_api.app_defines import API_STATUS_CHOICES, API_STATUS_PENDING
class EVEAPIModel(models.Model):
"""
A simple abstract base class to set some consistent fields on the models
that are updated from the EVE API.
"""
api_last_updated = models.DateTimeField(blank=True, null=True,
verbose_name="Time last updated from API",
help_text="When this object was last updated from the EVE API.")
class Meta:
abstract = True
class EVEAccount(EVEAPIModel):
"""
Use this class to store EVE user account information. Note that its use is
entirely optional and up to the developer's discretion.
"""
user = models.ForeignKey(User, blank=True, null=True,
help_text="User that owns this account")
description = models.CharField(max_length=50, blank=True,
help_text="User-provided description.")
api_key = models.CharField(max_length=64, verbose_name="API Key")
api_user_id = models.IntegerField(verbose_name="API User ID")
characters = models.ManyToManyField("EVEPlayerCharacter", blank=True,
null=True)
api_status = models.IntegerField(choices=API_STATUS_CHOICES,
default=API_STATUS_PENDING,
verbose_name="API Status",
help_text="End result of the last attempt at updating this object from the API.")
def in_corp(self, corpid):
for char in self.characters.all():
if char.corporation_id == corpid:
return True
return False
class Meta:
app_label = 'eve_api'
verbose_name = 'EVE Account'
verbose_name_plural = 'EVE Accounts'
ordering = ['api_user_id']
class EVEPlayerCharacter(EVEAPIModel):
"""
Represents an individual player character within the game. Not to be
confused with an account.
"""
name = models.CharField(max_length=255, blank=True, null=False)
corporation = models.ForeignKey('EVEPlayerCorporation', blank=True, null=True)
# TODO: Choices field
race = models.IntegerField(blank=True, null=True)
# TODO: Choices field
gender = models.IntegerField(blank=True, null=True)
balance = models.FloatField("Account Balance", blank=True, null=True)
attrib_intelligence = models.IntegerField("Intelligence", blank=True,
null=True)
attrib_memory = models.IntegerField("Memory", blank=True, null=True)
attrib_charisma = models.IntegerField("Charisma", blank=True, null=True)
attrib_perception = models.IntegerField("Perception", blank=True, null=True)
attrib_willpower = models.IntegerField("Willpower", blank=True, null=True)
objects = EVEPlayerCharacterManager()
def __unicode__(self):
if self.name:
return "%s (%d)" % (self.name, self.id)
else:
return "(%d)" % self.id
def __str__(self):
return self.__unicode__()
class Meta:
app_label = 'eve_api'
verbose_name = 'Player Character'
verbose_name_plural = 'Player Characters'
class EVEPlayerAlliance(EVEAPIModel):
"""
Represents a player-controlled alliance. Updated from the alliance
EVE XML API puller at intervals.
"""
name = models.CharField(max_length=255, blank=True, null=False)
ticker = models.CharField(max_length=15, blank=True, null=False)
#executor_character = models.ForeignKey(EVECharacter, blank=True, null=False)
member_count = models.IntegerField(blank=True, null=True)
date_founded = models.DateField(blank=True, null=True)
objects = EVEPlayerAllianceManager()
class Meta:
app_label = 'eve_api'
ordering = ['date_founded']
verbose_name = 'Player Alliance'
verbose_name_plural = 'Player Alliances'
def __unicode__(self):
if self.name:
return "%s (%d)" % (self.name, self.id)
else:
return "(#%d)" % self.id
def __str__(self):
return self.__unicode__()
class EVEPlayerCorporation(EVEAPIModel):
"""
Represents a player-controlled corporation. Updated from a mixture of
the alliance and corporation API pullers.
"""
name = models.CharField(max_length=255, blank=True, null=True)
ticker = models.CharField(max_length=15, blank=True, null=True)
description = models.TextField(blank=True, null=True)
url = models.URLField(verify_exists=False, blank=True, null=True)
ceo_character = models.ForeignKey(EVEPlayerCharacter, blank=True, null=True)
#home_station = models.ForeignKey(EVEStation, blank=True, null=False)
alliance = models.ForeignKey(EVEPlayerAlliance, blank=True, null=True)
alliance_join_date = models.DateField(blank=True, null=True)
tax_rate = models.FloatField(blank=True, null=True)
member_count = models.IntegerField(blank=True, null=True)
shares = models.IntegerField(blank=True, null=True)
# Logo generation stuff
logo_graphic_id = models.IntegerField(blank=True, null=True)
logo_shape1 = models.IntegerField(blank=True, null=True)
logo_shape2 = models.IntegerField(blank=True, null=True)
logo_shape3 = models.IntegerField(blank=True, null=True)
logo_color1 = models.IntegerField(blank=True, null=True)
logo_color2 = models.IntegerField(blank=True, null=True)
logo_color3 = models.IntegerField(blank=True, null=True)
objects = EVEPlayerCorporationManager()
class Meta:
app_label = 'eve_api'
verbose_name = 'Player Corporation'
verbose_name_plural = 'Player Corporations'
def __str__(self):
if self.name:
return self.name
else:
return "Corp #%d" % self.id
def query_and_update_corp(self):
"""
Takes an EVEPlayerCorporation object and updates it from the
EVE API service.
"""
# Pull XML from the EVE API via eve_proxy.
dom = EVEPlayerCorporation.objects.api_corp_sheet_xml(self.id)
# Tuples of pairings of tag names and the attribute on the Corporation
# object to set the data to.
tag_mappings = (
('corporationName', 'name'),
('ticker', 'ticker'),
('url', 'url'),
('description', 'description'),
('memberCount', 'member_count'),
('graphicID', 'logo_graphic_id'),
('shape1', 'logo_shape1'),
('shape2', 'logo_shape2'),
('shape3', 'logo_shape3'),
('color1', 'logo_color1'),
('color2', 'logo_color2'),
('color3', 'logo_color3'),
)
# Iterate through the tag mappings, setting the values of the tag names
# (first member of the tuple) to the attribute named in the second member
# of the tuple on the EVEPlayerCorporation object.
for tag_map in tag_mappings:
try:
setattr(self, tag_map[1],
dom.getElementsByTagName(tag_map[0])[0].firstChild.nodeValue)
except AttributeError:
# This tag has no value, skip it.
continue
except IndexError:
# Something weird has happened
print " * Index Error:", tag_map[0]
continue
print "Updating", self.id, self.name
self.save()

9
eve_proxy/__init__.py Executable file
View File

@@ -0,0 +1,9 @@
VERSION = (0, 4)
# Dynamically calculate the version based on VERSION tuple
if len(VERSION)>2 and VERSION[2] is not None:
str_version = "%d.%d_%s" % VERSION[:3]
else:
str_version = "%d.%d" % VERSION[:2]
__version__ = str_version

9
eve_proxy/admin.py Executable file
View File

@@ -0,0 +1,9 @@
from django.contrib import admin
from eve_proxy.models import CachedDocument
class CachedDocumentAdmin(admin.ModelAdmin):
model = CachedDocument
list_display = ('url_path', 'time_retrieved', 'cached_until')
verbose_name = 'Cached Document'
verbose_name_plural = 'Cached Documents'
admin.site.register(CachedDocument, CachedDocumentAdmin)

126
eve_proxy/models.py Executable file
View File

@@ -0,0 +1,126 @@
import httplib
import urllib
import xml
from datetime import datetime, timedelta
from xml.dom import minidom
from django.db import models
from eve_api.api_exceptions import APIAuthException, APINoUserIDException
# You generally never want to change this unless you have a very good reason.
API_URL = 'api.eve-online.com'
class CachedDocumentManager(models.Manager):
"""
This manager handles querying or retrieving CachedDocuments.
"""
def cache_from_eve_api(self, cached_doc, url_path, params, no_cache=False):
"""
Connect to the EVE API server, send the request, and cache it to
a CachedDocument. This is typically not something you want to call
directly. Use api_query().
"""
headers = {"Content-type": "application/x-www-form-urlencoded"}
# This is the connection to the EVE API server.
conn = httplib.HTTPConnection(API_URL)
# Combine everything into an HTTP request.
conn.request("POST", url_path, params, headers)
# Retrieve the response from the server.
response = conn.getresponse()
# Save the response (an XML document) to the CachedDocument.
cached_doc.body = response.read()
try:
# Parse the response via minidom
dom = minidom.parseString(cached_doc.body)
except xml.parsers.expat.ExpatError:
print "XML Parser Error:"
print cached_doc.body
return
# Set the CachedDocument's time_retrieved and cached_until times based
# on the values in the XML response. This will be used in future
# requests to see if the CachedDocument can be retrieved directly or
# if it needs to be re-cached.
cached_doc.time_retrieved = datetime.utcnow()
cached_doc.cached_until = dom.getElementsByTagName('cachedUntil')[0].childNodes[0].nodeValue
# Finish up and return the resulting document just in case.
if no_cache == False:
cached_doc.save()
return dom
def api_query(self, url_path, params=None, no_cache=False):
"""
Transparently handles querying EVE API or retrieving the document from
the cache.
Arguments:
url_path: (string) Path to the EVE API page to query. For example:
/eve/ErrorList.xml.aspx
params: (dictionary/string) A dictionary of extra parameters to include.
May also be a string representation of
the query: userID=1&characterID=xxxxxxxx
"""
if type({}) == type(params):
# If 'params' is a dictionary, convert it to a URL string.
params = urllib.urlencode(params)
elif params == None or params.strip() == '':
# For whatever reason, EVE API freaks out if there are no parameters.
# Add a bogus parameter if none are specified. I'm sure there's a
# better fix for this.
params = 'odd_parm=1'
# Combine the URL path and the parameters to create the full query.
query_name = '%s?%s' % (url_path, params)
if no_cache:
# If no_cache is enabled, don't even attempt a lookup.
cached_doc = CachedDocument()
created = False
else:
# Retrieve or create a new CachedDocument based on the full URL
# and parameters.
cached_doc, created = self.get_or_create(url_path=query_name)
# EVE uses UTC.
current_eve_time = datetime.utcnow()
# Figure out if we need hit EVE API and re-cache, or just pull from
# the local cache (based on cached_until).
if no_cache or created or \
cached_doc.cached_until == None or \
current_eve_time > cached_doc.cached_until:
# Cache from EVE API
dom = self.cache_from_eve_api(cached_doc, url_path, params,
no_cache=no_cache)
else:
# Parse the document here since it was retrieved from the
# database cache instead of queried for.
dom = minidom.parseString(cached_doc.body)
# Check for the presence errors. Only check the bare minimum,
# generic stuff that applies to most or all queries. User-level code
# should check for the more specific errors.
error_node = dom.getElementsByTagName('error')
if error_node:
error_code = error_node[0].getAttribute('code')
# User specified an invalid userid and/or auth key.
if error_code == '203':
raise APIAuthException()
elif error_code == '106':
raise APINoUserIDException()
return cached_doc
class CachedDocument(models.Model):
"""
This is a cached XML document from the EVE API.
"""
url_path = models.CharField(max_length=255)
body = models.TextField()
time_retrieved = models.DateTimeField(blank=True, null=True)
cached_until = models.DateTimeField(blank=True, null=True)
# The custom manager handles the querying.
objects = CachedDocumentManager()

8
eve_proxy/urls.py Executable file
View File

@@ -0,0 +1,8 @@
from django.conf.urls.defaults import *
urlpatterns = patterns('eve_proxy.views',
# This view can be used just like EVE API's http://api.eve-online.com.
# Any parameters or URL paths are sent through the cache system and
# forwarded to the EVE API site as needed.
url(r'^', 'retrieve_xml', name='eve_proxy-retrieve_xml'),
)

22
eve_proxy/views.py Executable file
View File

@@ -0,0 +1,22 @@
from django.http import HttpResponse
from eve_proxy.models import CachedDocument
def retrieve_xml(request):
"""
A view that forwards EVE API requests through the cache system, either
retrieving a cached document or querying and caching as needed.
"""
# This is the URL path (minus the parameters).
url_path = request.META['PATH_INFO']
# The parameters attached to the end of the URL path.
params = request.META['QUERY_STRING']
if url_path == '/' or url_path == '':
# If they don't provide any kind of query, shoot a quick error message.
return HttpResponse('No API query specified.')
# The query system will retrieve a cached_doc that was either previously
# or newly cached depending on cache intervals.
cached_doc = CachedDocument.objects.api_query(url_path, params)
# Return the document's body as XML.
return HttpResponse(cached_doc.body, mimetype='text/xml')

11
manage.py Executable file
View File

@@ -0,0 +1,11 @@
#!/usr/bin/python
from django.core.management import execute_manager
try:
import settings # Assumed to be in the same directory.
except ImportError:
import sys
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
sys.exit(1)
if __name__ == "__main__":
execute_manager(settings)

85
settings.py Normal file
View File

@@ -0,0 +1,85 @@
# Django settings for login project.
DEBUG = True
TEMPLATE_DEBUG = DEBUG
ADMINS = (
# ('Your Name', 'your_email@domain.com'),
)
MANAGERS = ADMINS
DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
DATABASE_NAME = '/home/dreddit/www/login.dredd.it/login.db3' # Or path to database file if using sqlite3.
DATABASE_USER = '' # Not used with sqlite3.
DATABASE_PASSWORD = '' # Not used with sqlite3.
DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
# If running in a Windows environment this must be set to the same as your
# system time zone.
TIME_ZONE = 'UTC'
# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = 'en-us'
SITE_ID = 1
# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = True
# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/"
MEDIA_ROOT = ''
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash if there is a path component (optional in other cases).
# Examples: "http://media.lawrence.com", "http://example.com/media/"
MEDIA_URL = ''
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
# trailing slash.
# Examples: "http://foo.com/media/", "/media/".
ADMIN_MEDIA_PREFIX = '/media/'
# Make this unique, and don't share it with anybody.
SECRET_KEY = '8i2+dd-b2tg9g%mq$&i$-8beh4i5^2mm=e-nh^$p47^w=z1igr'
# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.load_template_source',
'django.template.loaders.app_directories.load_template_source',
# 'django.template.loaders.eggs.load_template_source',
)
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
)
ROOT_URLCONF = 'login.urls'
TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
)
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'registration',
'eve_proxy',
'eve_api',
'sso',
)
AUTH_PROFILE_MODULE = 'sso.UserProfile'

0
sso/__init__.py Normal file
View File

18
sso/models.py Normal file
View File

@@ -0,0 +1,18 @@
from django.db import models
from django.contrib.auth.models import User
from eve_api.models.api_player import EVEAccount
class UserProfile(User):
eveaccount = models.ForeignKey(EVEAccount)
class Site(models.Model):
url = models.CharField(max_length=200)
active = models.BooleanField()
api = models.CharField(max_length=200)
class SiteAccount(models.Model):
user = models.ForeignKey(UserProfile)
site = models.ForeignKey(Site)
username = models.CharField(max_length=200)
active = models.BooleanField()

23
sso/tests.py Normal file
View File

@@ -0,0 +1,23 @@
"""
This file demonstrates two different styles of tests (one doctest and one
unittest). These will both pass when you run "manage.py test".
Replace these with more appropriate tests for your application.
"""
from django.test import TestCase
class SimpleTest(TestCase):
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.failUnlessEqual(1 + 1, 2)
__test__ = {"doctest": """
Another way to test that 1 + 1 is equal to 2.
>>> 1 + 1 == 2
True
"""}

1
sso/views.py Normal file
View File

@@ -0,0 +1 @@
# Create your views here.

11
urls.py Normal file
View File

@@ -0,0 +1,11 @@
from django.conf.urls.defaults import *
# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
(r'^login/', include('django.contrib.auth.views.login')),
(r'^admin/', include(admin.site.urls)),
)