[django-eve-commit] [django-eve-api] r25 committed - Bringing in wallet transactions, more general organization stuff. Star...

2 views
Skip to first unread message

django-...@googlecode.com

unread,
Apr 21, 2010, 1:52:14 PM4/21/10
to django-eve...@googlegroups.com
Revision: 25
Author: l11gctaylor
Date: Wed Apr 21 10:51:08 2010
Log: Bringing in wallet transactions, more general organization stuff.
Starting to settle in.
http://code.google.com/p/django-eve-api/source/detail?r=25

Added:
/trunk/eve_api/api_puller/character/wallet_journal.py
Deleted:
/trunk/eve_api/api_exceptions.py
Modified:
/trunk/eve_api/admin.py
/trunk/eve_api/api_puller/account/character_list.py
/trunk/eve_api/api_puller/corporation/corporation_sheet.py
/trunk/eve_api/api_puller/eve/character_id.py
/trunk/eve_api/api_puller/poop.py
/trunk/eve_api/api_puller/util.py
/trunk/eve_api/models/character.py
/trunk/eve_api/models/corporation.py
/trunk/eve_api/models/eve.py

=======================================
--- /dev/null
+++ /trunk/eve_api/api_puller/character/wallet_journal.py Wed Apr 21
10:51:08 2010
@@ -0,0 +1,160 @@
+"""
+/char/WalletJournal.xml.aspx
+
+http://wiki.eve-id.net/APIv2_Char_JournalEntries_XML
+"""
+from datetime import datetime
+from xml.etree import ElementTree
+from eve_proxy.models import CachedDocument
+from eve_db.models import StaStation, CrpNPCCorporation
+from eve_api.api_puller.util import get_api_model_class, parse_api_datetime
+
+def __resolve_generic_relation(generic_id, generic_name,
ApiPlayerCharacter,
+ ApiPlayerCorporation):
+ """
+ Given ownerID1 or ownerID2, determine the object that its pointing to.
+ """
+ if generic_id == '0':
+ # This is probably something like a Market Escrow, where there is
+ # no real owner2.
+ return None
+
+ try:
+ # Look through existing characters first, since the bulk of
+ # transactions happen between players.
+ character = ApiPlayerCharacter.objects.get(id=generic_id)
+ if not character.name:
+ # This guy is missing a name, save it.
+ character.name = generic_name
+ character.save()
+ return character
+ except:
+ # This doesn't match any current characters, but doesn't
necessarily
+ # rule the possibility out.
+ pass
+
+ try:
+ # NPC Corporations are static data, so this is a quick and
+ # easy thing to check and rule out.
+ return CrpNPCCorporation.objects.get(id=generic_id)
+ except:
+ # Not an NPC corporation. We can completely rule this possiblity
out.
+ pass
+
+ try:
+ # See if there's already a player corporation on record for this
+ # transaction.
+ return ApiPlayerCorporation.objects.get(id=generic_id)
+ except:
+ # Not a currently recorded player corporation.
+ pass
+ try:
+ return ApiPlayerCorporation.api.get_via_id(generic_id,
no_cache=True)
+ except ApiPlayerCorporation.DoesNotExist:
+ # Definitely not a player corporation.
+ pass
+
+ # It must be a character at this point, we've ruled out all of the
other
+ # scenarios.
+ character, created =
ApiPlayerCharacter.objects.get_or_create(id=generic_id)
+ if not character.name:
+ character.name = generic_name
+ character.save()
+ return character
+
+def __import_transaction(elem, ApiJournalTransaction, ApiJournalRefType,
+ ApiPlayerCorporation):
+ """
+ Given an ElementTree Element, parse it for Journal transaction data and
+ import it to the DB.
+ """
+ ref_id = elem.get('refID')
+ ref_type_id = elem.get('refTypeID')
+
+ transaction, created =
ApiJournalTransaction.objects.get_or_create(id=ref_id)
+ transaction.transaction_time = parse_api_datetime(elem.get('date'))
+ ref_type, created =
ApiJournalRefType.objects.get_or_create(id=ref_type_id)
+ transaction.ref_type = ref_type
+ transaction.owner_name1 = elem.get('ownerName1')
+ transaction.owner_name2 = elem.get('ownerName2')
+ transaction.arg_name = elem.get('argName1')
+ transaction.arg_id = elem.get('argID1')
+ transaction.amount = elem.get('amount')
+ transaction.balance = elem.get('balance')
+ transaction.reason = elem.get('reason')
+
+ # For things like bounties, taxes get paid to the player's corp.
+ tax_amount = elem.get('taxAmount')
+ if tax_amount:
+ transaction.tax_amount = tax_amount
+ tax_receiver_id = elem.get('taxReceiverID')
+ if tax_receiver_id:
+ tax_receiver, created =
ApiPlayerCorporation.objects.get_or_create(id=tax_receiver_id)
+ transaction.tax_receiver = tax_receiver
+
+ # Get around circular dependencies.
+ ApiPlayerCharacter = get_api_model_class('ApiPlayerCharacter')
+ ApiPlayerCorporation = get_api_model_class('ApiPlayerCorporation')
+
+ # Resolve the generic relationships by the ownerID* values.
+ transaction.owner1 = __resolve_generic_relation(elem.get('ownerID1'),
+
transaction.owner_name1,
+ ApiPlayerCharacter,
+ ApiPlayerCorporation)
+ transaction.owner2 = __resolve_generic_relation(elem.get('ownerID2'),
+
transaction.owner_name2,
+ ApiPlayerCharacter,
+ ApiPlayerCorporation)
+
+ # This also saves changes.
+ transaction.set_api_last_updated()
+
+def query_character_journal(character_or_id, account_key=1000,
+ before_ref_id=None, **kwargs):
+ """
+ This function queries a character's journal and creates/updates
+ ApiJournalTransaction objects found therein.
+ """
+ try:
+ # If the user has provided an int, this will fail.
+ id = character_or_id.id
+ # User has provided a corp object, use that instead of looking one
up.
+ character = character_or_id
+ except AttributeError:
+ # User provided an int, no corp object provided.
+ id = character_or_id
+ character = None
+
+ # This raises a ApiAccount.DoesNotExist if there is no match.
+ account = character.get_account()
+
+ # Assemble GET keys.
+ query_params = {'userID': account.api_user_id,
+ 'apiKey': account.api_key,
+ 'characterID': character.id,
+ 'accountKey': account_key}
+
+ if before_ref_id:
+ # This allows for walking journal transactions backwards.
+ # http://wiki.eve-id.net/APIv2_Char_JournalEntries_XML
+ query_params['beforeRefID'] = before_ref_id
+
+ # Retrieve the XML for the query from the API or the cache.
+ corp_doc =
CachedDocument.objects.api_query('/char/WalletJournal.xml.aspx',
+ params=query_params,
**kwargs)
+ # Parse the XML response.
+ tree = ElementTree.fromstring(corp_doc.body)
+
+ # List of <row> elements, each being a transaction.
+ transactions = tree.find('result/rowset').getchildren()
+
+ # The following calls get around circular imports.
+ ApiJournalTransaction = get_api_model_class('ApiJournalTransaction')
+ ApiJournalRefType = get_api_model_class('ApiJournalRefType')
+ ApiPlayerCorporation = get_api_model_class('ApiPlayerCorporation')
+
+ for transaction in transactions:
+ # Hand off importing logic.
+ __import_transaction(transaction, ApiJournalTransaction,
+ ApiJournalRefType, ApiPlayerCorporation)
+
=======================================
--- /trunk/eve_api/api_exceptions.py Fri Apr 9 13:28:15 2010
+++ /dev/null
@@ -1,12 +0,0 @@
-"""
-Contains exeptions used in the eve_api app.
-"""
-class APIInvalidCorpIDException(Exception):
- """
- Raised 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)
=======================================
--- /trunk/eve_api/admin.py Mon Apr 19 07:19:53 2010
+++ /trunk/eve_api/admin.py Wed Apr 21 10:51:08 2010
@@ -7,6 +7,7 @@
class ApiAccountAdmin(admin.ModelAdmin):
list_display = ('id', 'user')
search_fields = ['id']
+ readonly_fields = ('user', 'characters')
admin.site.register(ApiAccount, ApiAccountAdmin)

class ApiPlayerCharacterAdmin(admin.ModelAdmin):
@@ -43,6 +44,14 @@
admin.site.register(ApiPlayerCorporationWalletDivision,
ApiPlayerCorporationWalletDivisionAdmin)

+class ApiJournalTransactionAdmin(admin.ModelAdmin):
+ list_display =
('id', 'ref_type', 'owner_name1', 'owner_name2', 'amount',
+ 'transaction_time')
+ search_fields = ['id']
+ readonly_fields = ('id', 'owner_id1', 'owner_id2',
+ 'owner_type1', 'owner_type2', 'tax_receiver')
+admin.site.register(ApiJournalTransaction, ApiJournalTransactionAdmin)
+
class ApiJournalRefTypeAdmin(admin.ModelAdmin):
list_display = ('id', 'name')
search_fields = ['name']
=======================================
--- /trunk/eve_api/api_puller/account/character_list.py Fri Apr 16 10:29:58
2010
+++ /trunk/eve_api/api_puller/account/character_list.py Wed Apr 21 10:51:08
2010
@@ -10,7 +10,6 @@
from django.conf import settings
from eve_api.api_puller.util import get_api_model_class
from eve_proxy.models import CachedDocument
-from eve_proxy.proxy_exceptions import APIAuthException,
APINoUserIDException
from eve_api.app_defines import API_STATUS_OK

def _populate_characters(account, characters_node_children):
@@ -50,11 +49,6 @@
of querying for one. This is used to update ApiAccount
objects in place.
"""
- if not user_id:
- raise APINoUserIDException()
- if not api_key:
- raise APIAuthException()
-
auth_params = {'userID': user_id, 'apiKey': api_key}
account_doc =
CachedDocument.objects.api_query('/account/Characters.xml.aspx',
params=auth_params,
=======================================
--- /trunk/eve_api/api_puller/corporation/corporation_sheet.py Fri Apr 16
08:27:52 2010
+++ /trunk/eve_api/api_puller/corporation/corporation_sheet.py Wed Apr 21
10:51:08 2010
@@ -6,6 +6,7 @@
from xml.etree import ElementTree
from eve_proxy.models import CachedDocument
from eve_db.models import StaStation
+from eve_proxy.api_error_exceptions import APIQueryErrorException
from eve_api.api_puller.util import get_api_model_class

def __transfer_common_values(tree, corp):
@@ -137,7 +138,7 @@
query_params = {}
if query_character:
# Character provided, provide their credentials.
- account = query_character.apiaccount_set.all()[0]
+ account = query_character.get_account()
query_params['userID'] = account.api_user_id
query_params['apiKey'] = account.api_key
query_params['characterID'] = query_character.id
@@ -145,8 +146,16 @@
# Outsider is looking for details on a corp.
query_params['corporationID'] = id

- corp_doc =
CachedDocument.objects.api_query('/corp/CorporationSheet.xml.aspx',
- params=query_params,
**kwargs)
+ ApiPlayerCorporation = get_api_model_class("apiplayercorporation")
+ try:
+ corp_doc =
CachedDocument.objects.api_query('/corp/CorporationSheet.xml.aspx',
+ params=query_params,
**kwargs)
+ except APIQueryErrorException, exc:
+ if exc.code == 523:
+ # The specified corp doesn't exist.
+ raise ApiPlayerCorporation.DoesNotExist('No such corporation
with ID: %s' % id)
+ # Was some other error, re-raise it.
+ raise
#corp_dat = corp_doc.body.decode("utf-8", "replace")
#u_attr = unicode(corp_doc.body, 'ascii')
#corp_dat = u_attr.encode("utf-8", "replace")
@@ -155,17 +164,9 @@
#print "RAW", corp_doc.body
tree = ElementTree.fromstring(corp_doc.body)

- error_node = tree.find('result/error')
- # If there's an error, see if it's because the corp doesn't exist.
- if error_node:
- error_code = error_node.get('code')
- if error_code == '523':
- raise APIInvalidCorpIDException(id)
-
if not corp:
# User did not provide a corporation object, find or create one
# to update and return.
- ApiPlayerCorporation = get_api_model_class("apiplayercorporation")
corp, created =
ApiPlayerCorporation.objects.get_or_create(id=int(id))

__transfer_common_values(tree, corp)
=======================================
--- /trunk/eve_api/api_puller/eve/character_id.py Fri Apr 16 08:25:08 2010
+++ /trunk/eve_api/api_puller/eve/character_id.py Wed Apr 21 10:51:08 2010
@@ -1,36 +1,70 @@
from xml.etree import ElementTree
from eve_proxy.models import CachedDocument

-def query_get_object_from_name(child_model, name, **kwargs):
+def query_get_object_from_name(child_model, names,
update_missing_names=True,
+ fail_silently=False, **kwargs):
"""
Queries the EVE API looking for the ID of the specified corporation,
alliance, or character based on its name. This is not case sensitive.

+ Returns a list of updated objects, or an empty list if there were
+ no matches.
+
NOTE: Type checking is not possible from the API. Be very careful where
you use this, or you might end up with a Player object that has
the ID of an Alliance that will never be populated.

- name: (str) Name to search for.
+ child_model: (Model) Model of the type of object we're querying for.
+ names: (str or list) At least one name to search for. A list of names
+ may be used to query in bulk.
+ update_missing_names: (bool) If an object instance doesn't already
have a
+ name set, set/save it if this is True.
+ fail_silently: (bool) In the case where no match could be found, if
this is
+ False, raise a DoesNotExist exception. If True,
just
+ continue to the next name and don't return the
+ missing object in the results list.
"""
+ # We have to be able to accept a single name in the form of a string,
or
+ # a list of strings to look up in bulk.
+ if hasattr(names, 'pop'):
+ # Looks like a list.
+ names_str = ','.join(names)
+ else:
+ # Probably a string.
+ names_str = names
+
query_doc =
CachedDocument.objects.api_query('/eve/CharacterID.xml.aspx',
- params={'names': name},
+ params={'names':
names_str},
**kwargs)
query_dat = query_doc.body.decode("utf-8", "replace")
tree = ElementTree.fromstring(query_dat)
-
- id_node = tree.find('result/rowset/row')
- object_id = id_node.get('characterID')
-
- if object_id == '0':
- raise child_model.DoesNotExist('API returned no matches for the
given name.')
- else:
- ret_obj, created =
child_model.objects.get_or_create(id=int(object_id))
-
- if ret_obj.name != name:
- # If there is no name set on the object, update it to match
what the
- # user provided, since it matched the EVE API.
- ret_obj.name = name
- ret_obj.save()
-
- # Return reference to the object, with name set.
- return ret_obj
+ rowset_node = tree.find('result/rowset')
+ row_nodes = rowset_node.getchildren()
+
+ # Store the queried objects in here to return later.
+ query_results = []
+ for result_node in row_nodes:
+ object_id = result_node.get('characterID')
+ object_name = result_node.get('name')
+
+ if object_id == '0' and not fail_silently:
+ # Failing a match noisily.
+ error_msg = 'API returned no matches for the given name: %s' %
object_name
+ raise child_model.DoesNotExist(error_msg)
+ elif object_id == '0':
+ # Failing silently, just omit this one from the results.
+ continue
+ else:
+ # This is a good result.
+ ret_obj, created =
child_model.objects.get_or_create(id=int(object_id))
+
+ if update_missing_names and ret_obj.name != object_name:
+ # If there is no name set on the object, update it to
match what the
+ # user provided, since it matched the EVE API.
+ ret_obj.name = object_name
+ ret_obj.save()
+
+ # Return reference to the object, with name set.
+ query_results.append(ret_obj)
+
+ return query_results
=======================================
--- /trunk/eve_api/api_puller/poop.py Mon Apr 19 07:19:53 2010
+++ /trunk/eve_api/api_puller/poop.py Wed Apr 21 10:51:08 2010
@@ -2,6 +2,7 @@
from importer_path import fix_environment
fix_environment()
from eve_api.models import *
+from eve_proxy.models import CachedDocument
"""
corp = ApiPlayerCorporation.api.get_via_name("Blackman Industries",
no_cache=False)
@@ -24,10 +25,16 @@
account = ApiAccount.objects.all()[0]
account.update_from_api()
"""
-ApiJournalRefType.api.update_all_types()
+#ApiJournalRefType.api.update_all_types()

#print ApiPlayerCharacter.api.get_via_name("Ilyk Halibut")
#ApiPlayerAlliance.api.update_all_alliances(no_cache=True)
#print ApiPlayerAlliance.api.get_via_name("Atlas Alliance")
#ApiPlayerAlliance.api.update_all_corporations(no_cache=True)
#print "OPEN", ApiServer.api.get_status().online_players
+
+charact = ApiPlayerCharacter.objects.get(name='Ilyk Halibut')
+print charact
+charact.update_journal_from_api(no_cache=False)
+
+#CachedDocument.objects.clean_expired_entries()
=======================================
--- /trunk/eve_api/api_puller/util.py Fri Apr 16 07:54:01 2010
+++ /trunk/eve_api/api_puller/util.py Wed Apr 21 10:51:08 2010
@@ -1,6 +1,7 @@
"""
Various useful functions for the API pullers.
"""
+from datetime import datetime
from django.contrib.contenttypes.models import ContentType

def get_api_model_class(model_name):
@@ -11,3 +12,10 @@
model_name = model_name.lower()
return ContentType.objects.get(app_label="eve_api",
model=model_name).model_class()
+
+def parse_api_datetime(datetime_str):
+ """
+ Parses a datetime value from EVE's API.
+ """
+ dt = datetime.strptime(datetime_str, '%Y-%m-%d %H:%M:%S')
+ return dt
=======================================
--- /trunk/eve_api/models/character.py Fri Apr 16 08:25:08 2010
+++ /trunk/eve_api/models/character.py Wed Apr 21 10:51:08 2010
@@ -1,8 +1,11 @@
from django.db import models
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.contenttypes import generic
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from eve_api.api_puller.eve.character_id import query_get_object_from_name
-from eve_api.models.base import ApiModel
+from eve_api.api_puller.character import wallet_journal
+from eve_api.models import ApiModel

class ApiPlayerCharacterManager(models.Manager):
def get_via_name(self, name, **kwargs):
@@ -10,7 +13,7 @@
Returns the matching model, given a name. Note that there is no
way to type check this from the API, so be careful with this.
"""
- return query_get_object_from_name(ApiPlayerCharacter, name,
**kwargs)
+ return query_get_object_from_name(ApiPlayerCharacter, name,
**kwargs)[0]

class ApiPlayerCharacter(ApiModel):
"""
@@ -50,11 +53,60 @@

def get_account(self):
"""
- Returns the ApiAccount object that owns this character, or None if
- we don't know.
+ Returns the ApiAccount object that owns this character, or raise a
+ DoesNotExist exception if none exists.
"""
account = self.apiaccount_set.all()
if account:
return account[0]
else:
- return None
+ raise ApiPlayerCharacter.DoesNotExist('No account associated
with ApiPlayerCharacter ID#: %d' % self.id)
+
+ def update_journal_from_api(self, **kwargs):
+ """
+ Updates this character's wallet from the API.
+ """
+ wallet_journal.query_character_journal(self, **kwargs)
+
+class ApiJournalTransaction(ApiModel):
+ """
+ An individual journal transaction between players and/or corporations.
+ """
+ id = models.BigIntegerField(primary_key=True)
+ transaction_time = models.DateTimeField(blank=True, null=True)
+ ref_type = models.ForeignKey('ApiJournalRefType', blank=True,
null=True)
+
+ owner_name1 = models.CharField(max_length=255, blank=True)
+ owner_id1 = models.IntegerField(blank=True, null=True)
+ owner_type1 = models.ForeignKey(ContentType, blank=True, null=True,
+
related_name='journal_transact_owner1_set')
+ owner1 = generic.GenericForeignKey('owner_type1', 'owner_id1')
+
+ owner_name2 = models.CharField(max_length=255, blank=True)
+ owner_id2 = models.IntegerField(blank=True, null=True)
+ owner_type2 = models.ForeignKey(ContentType, blank=True, null=True,
+
related_name='journal_transact_owner2_set')
+ owner2 = generic.GenericForeignKey('owner_type2', 'owner_id2')
+
+ arg_name = models.CharField(max_length=255, blank=True)
+ arg_id = models.IntegerField(blank=True, null=True)
+ amount = models.FloatField(blank=True, null=True)
+ balance = models.FloatField(blank=True, null=True)
+ reason = models.TextField(blank=True)
+ tax_receiver = models.ForeignKey('ApiPlayerCorporation', blank=True,
+ null=True)
+ tax_amount = models.FloatField(blank=True, null=True)
+
+ objects = models.Manager()
+ #api = ApiPlayerCharacterManager()
+
+ class Meta:
+ app_label = 'eve_api'
+ verbose_name = 'Journal Transaction'
+ verbose_name_plural = 'Journal Transactions'
+
+ def __unicode__(self):
+ return "%s (%d)" % ("Transaction", self.id)
+
+ def __str__(self):
+ return self.__unicode__()
=======================================
--- /trunk/eve_api/models/corporation.py Fri Apr 16 08:25:08 2010
+++ /trunk/eve_api/models/corporation.py Wed Apr 21 10:51:08 2010
@@ -28,7 +28,7 @@
way to type check this from the API, so be careful with this.
"""
return
character_id.query_get_object_from_name(ApiPlayerCorporation,
- name, **kwargs)
+ name, **kwargs)[0]

class ApiPlayerCorporation(ApiModel):
"""
=======================================
--- /trunk/eve_api/models/eve.py Mon Apr 19 07:19:53 2010
+++ /trunk/eve_api/models/eve.py Wed Apr 21 10:51:08 2010
@@ -14,7 +14,7 @@
way to type check this from the API, so be careful with this.
"""
return character_id.query_get_object_from_name(ApiPlayerAlliance,
name,
- **kwargs)
+ **kwargs)[0]

def update_all_alliances(self, **kwargs):
"""


--
Subscription settings: http://groups.google.com/group/django-eve-commit-log/subscribe?hl=en
Reply all
Reply to author
Forward
0 new messages