Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Removed registration app, it is now a simple dependency; again lots o…

…f bit shifting throughout the project
  • Loading branch information...
commit aa0b99d6a0a1f3613156e2e5d6622e919d3b8565 1 parent e060ad6
@p2k p2k authored
Showing with 620 additions and 2,766 deletions.
  1. +82 −18 amqp_rpc_server.py
  2. +8 −0 linux_support/logrotate/pygowave-rpc
  3. +72 −0 linux_support/rc_script/pygowave-rpc
  4. +16 −0 media/css/mocha.css
  5. +2 −18 media/css/style.css
  6. BIN  media/images/debug.png
  7. +157 −9 pygowave_client/pygowave-client-controller.js
  8. +147 −35 pygowave_client/pygowave-client-view.js
  9. +2 −1  pygowave_server/admin.py
  10. +38 −8 pygowave_server/common/model.py
  11. +10 −9 pygowave_server/common/operations.py
  12. +52 −5 pygowave_server/models.py
  13. +0 −1  pygowave_server/urls.py
  14. +1 −27 pygowave_server/views.py
  15. 0  registration/__init__.py
  16. +0 −11 registration/admin.py
  17. +0 −134 registration/forms.py
  18. BIN  registration/locale/ar/LC_MESSAGES/django.mo
  19. +0 −81 registration/locale/ar/LC_MESSAGES/django.po
  20. BIN  registration/locale/bg/LC_MESSAGES/django.mo
  21. +0 −78 registration/locale/bg/LC_MESSAGES/django.po
  22. BIN  registration/locale/de/LC_MESSAGES/django.mo
  23. +0 −85 registration/locale/de/LC_MESSAGES/django.po
  24. BIN  registration/locale/el/LC_MESSAGES/django.mo
  25. +0 −84 registration/locale/el/LC_MESSAGES/django.po
  26. BIN  registration/locale/en/LC_MESSAGES/django.mo
  27. +0 −81 registration/locale/en/LC_MESSAGES/django.po
  28. BIN  registration/locale/es/LC_MESSAGES/django.mo
  29. +0 −85 registration/locale/es/LC_MESSAGES/django.po
  30. BIN  registration/locale/es_AR/LC_MESSAGES/django.mo
  31. +0 −83 registration/locale/es_AR/LC_MESSAGES/django.po
  32. BIN  registration/locale/fr/LC_MESSAGES/django.mo
  33. +0 −81 registration/locale/fr/LC_MESSAGES/django.po
  34. BIN  registration/locale/he/LC_MESSAGES/django.mo
  35. +0 −86 registration/locale/he/LC_MESSAGES/django.po
  36. BIN  registration/locale/is/LC_MESSAGES/django.mo
  37. +0 −74 registration/locale/is/LC_MESSAGES/django.po
  38. BIN  registration/locale/it/LC_MESSAGES/django.mo
  39. +0 −82 registration/locale/it/LC_MESSAGES/django.po
  40. BIN  registration/locale/ja/LC_MESSAGES/django.mo
  41. +0 −78 registration/locale/ja/LC_MESSAGES/django.po
  42. BIN  registration/locale/nl/LC_MESSAGES/django.mo
  43. +0 −77 registration/locale/nl/LC_MESSAGES/django.po
  44. BIN  registration/locale/pl/LC_MESSAGES/django.mo
  45. +0 −84 registration/locale/pl/LC_MESSAGES/django.po
  46. BIN  registration/locale/pt_BR/LC_MESSAGES/django.mo
  47. +0 −81 registration/locale/pt_BR/LC_MESSAGES/django.po
  48. BIN  registration/locale/ru/LC_MESSAGES/django.mo
  49. +0 −81 registration/locale/ru/LC_MESSAGES/django.po
  50. BIN  registration/locale/sr/LC_MESSAGES/django.mo
  51. +0 −80 registration/locale/sr/LC_MESSAGES/django.po
  52. BIN  registration/locale/sv/LC_MESSAGES/django.mo
  53. +0 −81 registration/locale/sv/LC_MESSAGES/django.po
  54. BIN  registration/locale/zh_CN/LC_MESSAGES/django.mo
  55. +0 −77 registration/locale/zh_CN/LC_MESSAGES/django.po
  56. BIN  registration/locale/zh_TW/LC_MESSAGES/django.mo
  57. +0 −77 registration/locale/zh_TW/LC_MESSAGES/django.po
  58. 0  registration/management/__init__.py
  59. 0  registration/management/commands/__init__.py
  60. +0 −19 registration/management/commands/cleanupregistration.py
  61. +0 −255 registration/models.py
  62. +0 −8 registration/signals.py
  63. +0 −355 registration/tests.py
  64. +0 −72 registration/urls.py
  65. +0 −153 registration/views.py
  66. +5 −1 settings.py
  67. +3 −0  templates/pygowave_server/index.html
  68. +25 −11 templates/pygowave_server/waves/on_the_wave.html
View
100 amqp_rpc_server.py
@@ -17,13 +17,14 @@
#
import sys, datetime, logging
+import logging.handlers
from carrot.connection import DjangoAMQPConnection
from carrot.messaging import Consumer, Publisher
from carrot.backends import DefaultBackend
from django.core.exceptions import ObjectDoesNotExist
-from pygowave_server.models import Participant, ParticipantConn, Gadget, GadgetElement
+from pygowave_server.models import Participant, ParticipantConn, Gadget, GadgetElement, Delta
from pygowave_server.common.operations import OpManager
from django.conf import settings
@@ -39,10 +40,17 @@
#
# --------
#
+# WAVELET_OPEN (sync)
+# PARTICIPANT_INFO (sync)
+# PARTICIPANT_SEARCH (sync)
# WAVELET_ADD_PARTICIPANT (sync)
# WAVELET_REMOVE_SELF (sync)
# DOCUMENT_ELEMENT_REPLACE (sync)
# DOCUMENT_ELEMENT_DELTA (async)
+# OPERATION_MESSAGE_BUNDLE (OT)
+# DOCUMENT_INSERT
+# DOCUMENT_DELETE
+# -> OPERATION_MESSAGE_BUNDLE_ACK
class PyGoWaveMessageProcessor(object):
"""
@@ -70,7 +78,7 @@ class PyGoWaveMessageProcessor(object):
"""
purge_every = datetime.timedelta(minutes=10)
- conn_min_lifetime = datetime.timedelta(minutes=getattr(settings, "ACCESS_KEY_TIMEOUT_MINUTES", 5))
+ conn_min_lifetime = datetime.timedelta(minutes=getattr(settings, "ACCESS_KEY_TIMEOUT_MINUTES", 2))
def __init__(self, connection):
self.consumer = Consumer(
@@ -95,10 +103,10 @@ def __init__(self, connection):
def broadcast(self, wavelet, type, property, except_connections=[]):
"""
- Send messages to all participants. Should only be used with synchronous
- events.
+ Send messages to all participants.
+
`wavelet` must be a Wavelet object.
- `except_participants` is a list of ParticipantConn objects to be
+ `except_connections` is a list of ParticipantConn objects to be
excluded from the broadcast.
"""
@@ -144,20 +152,21 @@ def receive(self, message_data, message):
logger.debug("Received Message from %s.%s.%s:\n%s" % (participant_conn_key, wavelet_id, message_category, repr(message_data)))
+ # Always ack the message or it will persist on errors
+ message.ack()
+
# Get participant connection
try:
pconn = ParticipantConn.objects.get(tx_key=participant_conn_key)
except ObjectDoesNotExist:
- logger.error("{%s} Error: ParticipantConn not found\n" % (rkey))
- message.ack()
+ logger.error("{%s} ParticipantConn not found" % (rkey))
return # Fail silently
# Get wavelet
try:
wavelet = pconn.participant.wavelets.get(id=wavelet_id)
except ObjectDoesNotExist:
- logger.error("{%s} Error: Wavelet not found (or not participating)\n" % (rkey))
- message.ack()
+ logger.error("{%s} Wavelet not found (or not participating)" % (rkey))
return # Fail silently
# Handle message and reply to sender and/or broadcast an event
@@ -171,8 +180,6 @@ def receive(self, message_data, message):
self.send(messages, "%s.%s.waveop" % (receiver, wavelet_id))
self.out_queue = {}
- message.ack()
-
# Cleanup time?
if datetime.datetime.now() > self.next_purge:
self.purge_connections()
@@ -196,7 +203,27 @@ def handle_participant_message(self, wavelet, pconn, message):
"wavelet": wavelet.serialize(),
"blips": wavelet.serialize_blips(),
})
+
+ elif message["type"] == "PARTICIPANT_INFO":
+ logger.info("[%s/%d@%s] Sending participant information" % (participant.name, pconn.id, wavelet.wave.id))
+ p_info = {}
+ for p_id in message["property"]:
+ try:
+ p_info[p_id] = Participant.objects.get(id=p_id).serialize()
+ except ObjectDoesNotExist:
+ p_info[p_id] = None
+ self.emit(pconn, "PARTICIPANT_INFO", p_info)
+
+ elif message["type"] == "PARTICIPANT_SEARCH":
+ if len(message["property"]) < getattr(settings, "PARTICIPANT_SEARCH_LENGTH"):
+ self.emit(pconn, "PARTICIPANT_INFO", {"result": "TOO_SHORT", "data": getattr(settings, "PARTICIPANT_SEARCH_LENGTH")})
+ logger.info("[%s/%d@%s] Performing participant search" % (participant.name, pconn.id, wavelet.wave.id))
+ lst = []
+ for p in Participant.objects.filter(name__icontains=message["property"]).exclude(id=participant.id):
+ lst.append(p.id)
+ self.emit(pconn, "PARTICIPANT_SEARCH", {"result": "OK", "data": lst})
+
elif message["type"] == "WAVELET_ADD_PARTICIPANT":
# Find participant
try:
@@ -276,6 +303,38 @@ def handle_participant_message(self, wavelet, pconn, message):
# Asynchronous event, so send to all part. except the sender
self.broadcast(wavelet, "DOCUMENT_ELEMENT_SETPREF", {"id": elt_id, "key": key, "value": value}, [pconn])
+
+ elif message["type"] == "OPERATION_MESSAGE_BUNDLE":
+ # Build OpManager
+ newdelta = OpManager(wavelet.wave.id, wavelet.id)
+ newdelta.unserialize(message["property"]["operations"])
+ version = message["property"]["version"]
+
+ # Transform
+ for delta in wavelet.deltas.filter(version__gt=version):
+ for op in delta.getOpManager().operations:
+ newdelta.transform(op) # Trash results (an existing delta cannot be changed)
+
+ # Apply
+ wavelet.applyOperations(newdelta.operations)
+
+ # Raise version and store
+ wavelet.version += 1
+ wavelet.save()
+
+ Delta.createByOpManager(newdelta, wavelet.version).save()
+
+ # Respond
+ self.emit(pconn, "OPERATION_MESSAGE_BUNDLE_ACK", wavelet.version)
+ self.broadcast(wavelet, "OPERATION_MESSAGE_BUNDLE", {"version": wavelet.version, "operations": newdelta.serialize()}, [pconn])
+
+ logger.info("[%s/%d@%s] Processed delta #%d -> v%d" % (participant.name, pconn.id, wavelet.wave.id, version, wavelet.version))
+
+ else:
+ logger.error("[%s/%d@%s] Unknown message: %s" % (participant.name, pconn.id, wavelet.wave.id, message))
+
+ else:
+ logger.error("[%s/%d@%s] Unknown message: %s" % (participant.name, pconn.id, wavelet.wave.id, message))
return True
@@ -330,15 +389,15 @@ def queue_exists(self, queue):
logger.setLevel(logging.CRITICAL)
if "--logfile" in sys.argv:
- info_handler = logging.FileHandler("/var/log/pygowave/info", 'a', 'utf-8')
- error_handler.setLevel(logging.INFO)
+ info_handler = logging.handlers.WatchedFileHandler("/var/log/pygowave/info.log", 'a', 'utf-8')
+ info_handler.setLevel(logging.INFO)
class InfoOnlyFilter:
def filter(self, record): return record.levelno == logging.INFO
info_handler.addFilter(InfoOnlyFilter())
info_handler.setFormatter(log_formatter)
logger.addHandler(info_handler)
- error_handler = logging.FileHandler("/var/log/pygowave/error", 'a', 'utf-8')
+ error_handler = logging.handlers.WatchedFileHandler("/var/log/pygowave/error.log", 'a', 'utf-8')
error_handler.setLevel(logging.ERROR)
error_handler.setFormatter(log_formatter)
logger.addHandler(error_handler)
@@ -355,7 +414,12 @@ def filter(self, record): return record.levelno == logging.INFO
# Python Ctrl-C handler
signal.signal(signal.SIGINT, signal.SIG_DFL)
- amqpconn = DjangoAMQPConnection()
- omc = PyGoWaveMessageProcessor(amqpconn)
- logger.info("=> RabbitMQ RPC Server ready <=")
- omc.wait()
+ try:
+ amqpconn = DjangoAMQPConnection()
+ omc = PyGoWaveMessageProcessor(amqpconn)
+ logger.info("=> RabbitMQ RPC Server ready <=")
+ omc.wait()
+ except:
+ import traceback
+ logger.critical("Crash!\n" + traceback.format_exc())
+ sys.exit(1)
View
8 linux_support/logrotate/pygowave-rpc
@@ -0,0 +1,8 @@
+# Configuration file for logrotate; rotates PyGoWave's RPC logfiles
+# To be placed into /etc/logrotate.d/
+
+/var/log/pygowave/*.log {
+ notifempty
+ missingok
+ size 10M
+}
View
72 linux_support/rc_script/pygowave-rpc
@@ -0,0 +1,72 @@
+#!/bin/bash
+
+#
+# rc-script for Arch Linux to launch PyGoWave at system startup
+#
+
+daemon_name="pygowave-rpc"
+daemon_title="PyGoWave Server RPC"
+
+. /etc/rc.conf
+. /etc/rc.d/functions
+
+get_pid() {
+ ps aux|awk '/[0-9] python amqp_rpc_server.py/ {print $2}'
+}
+
+case "$1" in
+ start)
+ stat_busy "Starting $daemon_title"
+
+ PID=$(get_pid)
+ if [ -z "$PID" ]; then
+ [ -f /var/run/$daemon_name.pid ] && rm -f /var/run/$daemon_name.pid
+ # RUN
+ sudo -u rabbitmq /srv/http/pygowave_project/launch-pygowave-rpc --logfile >/dev/null 2>/dev/null &
+ #
+ if [ $? -gt 0 ]; then
+ stat_fail
+ exit 1
+ else
+ echo $(get_pid) > /var/run/$daemon_name.pid
+ add_daemon $daemon_name
+ stat_done
+ fi
+ else
+ stat_fail
+ exit 1
+ fi
+ ;;
+
+ stop)
+ stat_busy "Stopping $daemon_title"
+ PID=$(get_pid)
+ # KILL
+ [ ! -z "$PID" ] && kill $PID &> /dev/null
+ #
+ if [ $? -gt 0 ]; then
+ stat_fail
+ exit 1
+ else
+ rm -f /var/run/$daemon_name.pid &> /dev/null
+ rm_daemon $daemon_name
+ stat_done
+ fi
+ ;;
+
+ restart)
+ $0 stop
+ sleep 1
+ $0 start
+ ;;
+
+ status)
+ stat_busy "Checking $daemon_title status";
+ ck_status $daemon_name
+ ;;
+
+ *)
+ echo "usage: $0 {start|stop|restart|status}"
+esac
+
+exit 0
View
16 media/css/mocha.css
@@ -176,6 +176,22 @@ div.mochaToolbarWrapper.bottom {
font-weight: bold;
}
+.mochaImageButton {
+ padding: 0.1em 0.3em;
+ margin: 0 0.4em 0.4em 0;
+ vertical-align: middle;
+}
+
+.mochaImageButton img {
+ float: left;
+ margin-right: 5px;
+}
+
+.mochaImageButton div {
+ float: left;
+ margin-top: 2px;
+}
+
.mochaTabsNav {
border: 0 none;
list-style-image: none;
View
20 media/css/style.css
@@ -347,26 +347,10 @@ p
font-weight: bold;
}
-#leave_button
+.button_box
{
float: right;
- height: 22px;
- padding: 4px;
- cursor: pointer;
-}
-
-#leave_button img
-{
- float: left;
-}
-
-#leave_button label
-{
- float: left;
- margin-left: 4px;
- margin-right: 4px;
- margin-top: 4px;
- cursor: pointer;
+ padding-left: 10px;
}
#gadget_container
View
BIN  media/images/debug.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
166 pygowave_client/pygowave-client-controller.js
@@ -92,11 +92,13 @@ pygowave.controller = function () {
this._iview = view;
this._iview.addEvent('textInserted', this._onTextInserted.bind(this));
this._iview.addEvent('textDeleted', this._onTextDeleted.bind(this));
+ this._iview.addEvent('searchForParticipant', this._onSearchForParticipant.bind(this));
this.waves = new Hash();
this.waves.set(model.id(), model);
this.wavelets = new Hash();
+ this.new_participants = new Array();
this.participants = new Hash();
// The connection object must be stored in this.conn and must have a sendJson and subscribeWavelet method (defined below).
@@ -143,7 +145,7 @@ pygowave.controller = function () {
* @param {int} code Error code provided by socket API.
*/
onConnClose: function (code) {
- alert('Lost Connection, Code: ' + code);
+ this._iview.showControllerError(gettext("The connection was lost.<br/><br/>Error code: %d").sprintf(code));
},
/**
@@ -165,6 +167,10 @@ pygowave.controller = function () {
* @param {object} obj JSON-decoded message object for processing.
*/
onConnReceive: function (wavelet_id, obj) {
+ var wavelet_model = null;
+ if (this.wavelets.has(wavelet_id))
+ wavelet_model = this.wavelets[wavelet_id].model;
+
for (var it = new _Iterator(obj);it.hasNext();) {
obj = it.next();
switch (obj.type) {
@@ -175,11 +181,31 @@ pygowave.controller = function () {
var wave_model = this.waves[wave_id];
wave_model.loadFromSnapshot(obj.property, this.participants);
this.wavelets[wavelet_id] = {
- model: wave_model,
- pending: false
+ model: wave_model.wavelet(wavelet_id),
+ pending: false,
+ blocked: false
};
this._setupOpManagers(wave_id, wavelet_id);
- this.fireEvent("waveletOpened", {"wave_id": wave_id, "wavelet_id": wavelet_id});
+ this.fireEvent("waveletOpened", [wave_id, wavelet_id]);
+
+ this._requestParticipantInfo(wavelet_id);
+ break;
+ case "OPERATION_MESSAGE_BUNDLE_ACK":
+ wavelet_model.options.version = obj.property;
+ this.wavelets[wavelet_id].mpending.fetch(); // Clear
+ if (!this.wavelets[wavelet_id].mcached.isEmpty())
+ this._transferOperations(wavelet_id);
+ else
+ this.wavelets[wavelet_id].pending = false;
+ break;
+ case "OPERATION_MESSAGE_BUNDLE":
+ this._processOperations(wavelet_model, obj.property.operations);
+ wavelet_model.options.version = obj.property.version;
+ break;
+ case "PARTICIPANT_INFO":
+ this._processParticipantsInfo(obj.property);
+ break;
+ case "PARTICIPANT_SEARCH":
break;
}
}
@@ -197,6 +223,27 @@ pygowave.controller = function () {
this.options.stompPassword
);
},
+ /**
+ * Returns if the given wavelet's transmission is blocked
+ * @function {public} isBlocked
+ * @param {String} wavelet_id ID of the Wavelet
+ */
+ isBlocked: function (wavelet_id) {
+ return this.wavelets[wavelet_id].blocked;
+ },
+ /**
+ * Block or unblock the transmission of messages for debugging purposes.
+ * @function {public} setBlocked
+ * @param {String} wavelet_id ID of the Wavelet whose messages should
+ * be blocked
+ * @param {Boolean} blocked Set to true to disable message transmission,
+ * false to re-enable it (may send queued messages).
+ */
+ setBlocked: function (wavelet_id, blocked) {
+ this.wavelets[wavelet_id].blocked = blocked;
+ if (!blocked && !this.wavelets[wavelet_id].pending && this.hasPendingOperations(wavelet_id))
+ this._transferOperations(wavelet_id);
+ },
/**
* Open a wavelet.
@@ -217,19 +264,61 @@ pygowave.controller = function () {
* @param {String} wavelet_id ID of the wavelet
*/
hasPendingOperations: function (wavelet_id) {
- return this.wavelets[wavelet_id].pending;
+ return this.wavelets[wavelet_id].pending || !this.wavelets[wavelet_id].mpending.isEmpty();
},
/**
* Collate the internal participant "database" with the given ID list.
+ * New participants will be added to the new_participants array, so
+ * they can be retrieved later.
* @function {private} _collateParticipants
* @param {String[]} id_list List of participant IDs
*/
_collateParticipants: function (id_list) {
for (var it = new _Iterator(id_list);it.hasNext();) {
var id = it.next();
- if (!this.participants.has(id))
+ if (!this.participants.has(id)) {
this.participants.set(id, new pygowave.model.Participant(id));
+ this.new_participants.append(id);
+ }
+ }
+ },
+
+ /**
+ * Requests information on participants from the server, which have not
+ * been retrieved yet. Reads from the new_participants array.
+ * @function {private} _requestParticipantInfo
+ * @param {String} wavelet_id ID of the Wavelet
+ */
+ _requestParticipantInfo: function (wavelet_id) {
+ if (this.new_participants.length == 0)
+ return;
+
+ this.conn.sendJson(wavelet_id, {
+ type: "PARTICIPANT_INFO",
+ property: this.new_participants
+ });
+ },
+
+ /**
+ * Callback from server after participant info request.
+ * @function {private} _processParticipantsInfo
+ * @param {Object} pmap Participants map
+ */
+ _processParticipantsInfo: function (pmap) {
+ for (var it = new _Iterator(pmap); it.hasNext(); ) {
+ var pdata = it.next(), id = it.key();
+ var i = this.new_participants.indexOf(id);
+ if (i != -1)
+ this.new_participants.pop(i);
+ var obj;
+ if (this.participants.has(id))
+ obj = this.participants[id];
+ else {
+ obj = new pygowave.model.Participant(id);
+ this.participants.set(id, obj);
+ }
+ obj.updateData(pdata);
}
},
@@ -260,9 +349,53 @@ pygowave.controller = function () {
_transferOperations: function (wavelet_id) {
var mpending = this.wavelets[wavelet_id].mpending;
var mcached = this.wavelets[wavelet_id].mcached;
- mpending.put(mcached.fetch());
- this.wavelets[wavelet_id].pending = true;
- //self.processOperations.emit(self.__version, simplejson.dumps(self.opsPending.serialize(False)))
+ var model = this.wavelets[wavelet_id].model;
+
+ if (mpending.isEmpty())
+ mpending.put(mcached.fetch());
+
+ if (!this.isBlocked(wavelet_id)) {
+ this.wavelets[wavelet_id].pending = true;
+
+ this.conn.sendJson(wavelet_id, {
+ type: "OPERATION_MESSAGE_BUNDLE",
+ property: {
+ version: model.options.version,
+ operations: mpending.serialize()
+ }
+ });
+ }
+ },
+ /**
+ * Process a message bundle from the server. Do transformation and
+ * apply it to the model.
+ *
+ * @function {private} _processOperations
+ * @param {pygowave.model.Wavelet} wavelet Wavelet model
+ * @param {Object[]} serial_ops Serialized operations
+ */
+ _processOperations: function (wavelet, serial_ops) {
+ var mpending = this.wavelets[wavelet.id()].mpending;
+ var mcached = this.wavelets[wavelet.id()].mcached;
+ var delta = new pygowave.operations.OpManager(wavelet.waveId(), wavelet.id());
+ delta.unserialize(serial_ops);
+
+ // Iterate over all operations
+ for (var incoming = new _Iterator(delta.operations); incoming.hasNext(); ) {
+ // Transform pending operations, iterate over results
+ for (var tr1 = new _Iterator(mpending.transform(incoming.next())); tr1.hasNext(); ) {
+ // Transform cached operations, iterate over results
+ for (var tr2 = new _Iterator(mcached.transform(tr1.next())); tr2.hasNext(); ) {
+ var op = tr2.next();
+ if (op.isNull()) continue;
+ // Apply operation
+ if (op.type == pygowave.operations.DOCUMENT_INSERT)
+ wavelet.blipById(op.blip_id).insertText(op.index, op.property);
+ else if (op.type == pygowave.operations.DOCUMENT_DELETE)
+ wavelet.blipById(op.blip_id).deleteText(op.index, op.property);
+ }
+ }
+ }
},
/**
@@ -276,6 +409,7 @@ pygowave.controller = function () {
*/
_onTextInserted: function (waveletId, blipId, index, content) {
this.wavelets[waveletId].mcached.documentInsert(blipId, index, content);
+ this.wavelets[waveletId].model.blipById(blipId).insertText(index, content, true);
},
/**
* Callback from view on text deletion.
@@ -288,6 +422,20 @@ pygowave.controller = function () {
*/
_onTextDeleted: function (waveletId, blipId, start, end) {
this.wavelets[waveletId].mcached.documentDelete(blipId, start, end);
+ this.wavelets[waveletId].model.blipById(blipId).deleteText(start, end-start, true);
+ },
+ /**
+ * Callback from view on searching.
+ *
+ * @function {private} _onSearchForParticipant
+ * @param {String} waveletId ID of the Wavelet
+ * @param {String} text Entered search query
+ */
+ _onSearchForParticipant: function (waveletId, text) {
+ this.conn.sendJson(waveletId, {
+ type: "PARTICIPANT_SEARCH",
+ property: text
+ });
}
});
View
182 pygowave_client/pygowave-client-view.js
@@ -115,8 +115,10 @@ pygowave.view = function () {
this._input = new Element('input', {'type': 'text', 'class': 'inactive'}).inject(this._iwrapper);
this._input.addEvent('focus', this._onFocus.bind(this));
this._input.addEvent('blur', this._onBlur.bind(this));
+ this._input.addEvent('keypress', this._onChange.bind(this));
this._rcorner = new Element('div', {'class': 'right_corner inactive'}).inject(contentElement);
this.parent(parentElement, contentElement, where);
+ this._last_value = "";
},
/**
@@ -146,7 +148,11 @@ pygowave.view = function () {
* @function {private} _onChange
*/
_onChange: function () {
- this.fireEvent('change', this.text());
+ var new_value = this.text();
+ if (new_value != this._last_value) {
+ this.fireEvent('change', new_value);
+ this._last_value = new_value;
+ }
},
/**
@@ -187,6 +193,7 @@ pygowave.view = function () {
*/
initialize: function (view, parentElement, participant, small, where) {
this._view = view;
+ this._participant = participant;
var contentElement = new Element('div', {'class': 'wavelet_participant'});
if (small) contentElement.addClass('small');
var tn = participant.thumbnailUrl();
@@ -201,6 +208,7 @@ pygowave.view = function () {
if (small) this._pImage.addClass('small');
this.parent(parentElement, contentElement, where);
participant.addEvent('onlineStateChanged', this._onOnlineStateChanged.bind(this));
+ participant.addEvent('dataChanged', this._onDataChanged.bind(this));
this._onOnlineStateChanged(participant.isOnline());
},
@@ -217,6 +225,29 @@ pygowave.view = function () {
this._oImage = new Element('div', {'class': 'indicator'}).inject(this.contentElement, 'bottom');
this._oImage.addClass(online ? 'online' : 'offline').removeClass(online ? 'offline' : 'online');
+ },
+
+ /**
+ * Callback for {@link pygowave.model.Participant.onDataChanged
+ * onDataChanged}. Updates the representation of the participant.
+ * @function {private} _onDataChanged
+ */
+ _onDataChanged: function () {
+ var pImage = this._pImage;
+ pImage.set("alt", this._participant.displayName());
+ pImage.set("title", this._participant.displayName());
+
+ var tn = this._participant.thumbnailUrl();
+ if (tn == "")
+ tn = this._view.defaultThumbnailUrl();
+
+ if (pImage.get("src") != tn) { // Exchange thumbnail
+ new Fx.Tween(pImage, {duration:200}).start("opacity", 1, 0).chain(function () {
+ pImage.set("src", "");
+ pImage.set.delay(100, pImage, ["src", tn]);
+ this.start.delay(100, this ,["opacity", 0, 1]);
+ });
+ }
}
});
@@ -234,12 +265,14 @@ pygowave.view = function () {
/**
* Called on instantiation.
* @constructor {public} initialize
- * @param {OpManager} pending A reference to the pending operations manager
- * @param {OpManager} cached A reference to the cached operations manager
+ * @param {pygowave.controller.PyGoWaveClient} controller The controller
+ * @param {String} wavelet_id ID of the wavelet to monitor
*/
- initialize: function (pending, cached) {
- this._mpending = pending;
- this._mcached = cached;
+ initialize: function (controller, wavelet_id) {
+ this._controller = controller;
+ this._wavelet_id = wavelet_id;
+ this._mpending = controller.wavelets[wavelet_id].mpending;
+ this._mcached = controller.wavelets[wavelet_id].mcached;
this._content = new Element('div', {'class': 'debug_window'});
@@ -271,6 +304,15 @@ pygowave.view = function () {
new Element('td', {'class': 'col3'}).inject(elt);
new Element('td', {'class': 'col4'}).inject(elt);
+ // Bind callbacks
+ for (var property in this) {
+ if (property.startswith("cached_on") || property.startswith("pending_on") || property.startswith("_on"))
+ this[property] = this[property].bind(this);
+ }
+
+ var buttons = {};
+ var buttonText = gettext(controller.isBlocked(wavelet_id) ? "Unblock" : "Block");
+ buttons[buttonText] = this._onBlock;
this.parent({
title: gettext("Operations Viewer (Debug)"),
content: this._content,
@@ -287,17 +329,11 @@ pygowave.view = function () {
resizable: true,
footerHeight: 34,
padding: {top: 0, right: 0, bottom: 0, left: 0},
- onClose: this._onClose.bind(this)
+ buttons: buttons
});
this._content.getParent().setStyle("height", "100%");
this.addEvent('closeComplete', this._onClose.bind(this));
- // Bind callbacks
- for (var property in this) {
- if (property.startswith("cached_on") || property.startswith("pending_on"))
- this[property] = this[property].bind(this);
- }
-
// Connect callbacks
this._mcached.addEvent('beforeOperationsInserted', this.cached_onBeforeOperationsInserted);
this._mcached.addEvent('afterOperationsInserted', this.cached_onAfterOperationsInserted);
@@ -307,6 +343,16 @@ pygowave.view = function () {
this._mpending.addEvent('afterOperationsInserted', this.pending_onAfterOperationsInserted);
this._mpending.addEvent('afterOperationsRemoved', this.pending_onAfterOperationsRemoved);
this._mpending.addEvent('operationChanged', this.pending_onOperationChanged);
+
+ // Populate table
+ if (!this._mcached.isEmpty()) {
+ this.cached_onBeforeOperationsInserted(0, this._mcached.operations.length-1);
+ this.cached_onAfterOperationsInserted(0, this._mcached.operations.length-1);
+ }
+ if (!this._mpending.isEmpty()) {
+ this.pending_onBeforeOperationsInserted(0, this._mpending.operations.length-1);
+ this.pending_onAfterOperationsInserted(0, this._mpending.operations.length-1);
+ }
},
/**
@@ -324,6 +370,20 @@ pygowave.view = function () {
this._mpending.removeEvent('afterOperationsRemoved', this.pending_onAfterOperationsRemoved);
this._mpending.removeEvent('operationChanged', this.pending_onOperationChanged);
},
+ /**
+ * Called if the "Block" button was clicked.
+ * @function {private} _onBlock
+ */
+ _onBlock: function () {
+ if (!this._controller.isBlocked(this._wavelet_id)) {
+ this._controller.setBlocked(this._wavelet_id, true);
+ this.buttonsEl.childNodes[0].set('text', gettext("Unblock"));
+ }
+ else {
+ this._controller.setBlocked(this._wavelet_id, false);
+ this.buttonsEl.childNodes[0].set('text', gettext("Block"));
+ }
+ },
/**
* Generic helper function.
@@ -517,7 +577,7 @@ pygowave.view = function () {
* @param {String} text Entered query text
*/
_onQueryChange: function (text) {
- this._view.fireEvent('searchForParticipant', text);
+ this._view.fireEvent('searchForParticipant', [this._view.model.rootWavelet().id(), text]);
}
});
@@ -592,6 +652,12 @@ pygowave.view = function () {
this._blip = blip;
this._lastRange = [0, 0];
this._lastContent = "";
+
+ this._onInsertText = this._onInsertText.bind(this);
+ this._onDeleteText = this._onDeleteText.bind(this);
+
+ blip.addEvent('insertText', this._onInsertText);
+ blip.addEvent('deleteText', this._onDeleteText);
},
editorKeyUp: function(e) {
var newContent = this.contentToString();
@@ -673,14 +739,14 @@ pygowave.view = function () {
* @function {public String} contentToString
*/
contentToString: function () {
- var cleaned = this.getContent().replace(/<br[^>]*>/gi, "\n");
+ var cleaned = this.getContent().replace(/<br[^>]*>/gi, "\n").replace("&nbsp;", " ");
if (cleaned.endswith("\n"))
cleaned = cleaned.slice(0, cleaned.length-1);
return cleaned;
},
/**
- * Returns the current selected text range as [start, end]. This treats
- * all element nodes as one character.
+ * Returns the current selected text range as [start, end]. This
+ * ignores all element nodes except br (which is treated as 1 character).
* @function {public Array} currentTextRange
*/
currentTextRange: function () {
@@ -691,9 +757,26 @@ pygowave.view = function () {
return [start, end];
},
+
+ _onInsertText: function (index, text) {
+ var ret = this._walkDown(this.doc.body, index);
+ var elt = ret[0], offset = ret[1];
+ if ($type(elt) == "textnode" || $type(elt) == "whitespace")
+ elt.insertData(offset, text);
+ else
+ elt.parentNode.insertBefore(document.createTextNode(text), elt);
+ },
+ _onDeleteText: function (index, length) {
+ var ret = this._walkDown(this.doc.body, index);
+ var elt = ret[0], offset = ret[1];
+ if ($type(elt) == "textnode")
+ elt.deleteData(offset, length);
+ },
+
/**
- * Walk up the DOM tree while summing up all text lengths. Elements
- * count 1 for start and 1 for end tags. Returns the absolute offset.
+ * Walk up the DOM tree while summing up all text lengths. Elements are
+ * ignored, except br and iframe which count as one character.
+ * Returns theabsolute offset.
*
* @function {private int} _walkUp
* @param {Node} node The node to start
@@ -722,15 +805,17 @@ pygowave.view = function () {
while ((node = node.previousSibling) != null) {
prev = node;
if ($type(node) == "element") {
- offset++;
- // descend
- while ((node = node.lastChild) != null) {
- prev = node;
- if ($type(node) == "textnode") {
- offset += node.textContent.length;
- break;
- }
+ if (node.get('tag') == "br" || node.get('tag') == "iframe")
offset++;
+ else {
+ // descend
+ while ((node = node.lastChild) != null) {
+ prev = node;
+ if ($type(node) == "textnode") {
+ offset += node.textContent.length;
+ break;
+ }
+ }
}
node = prev;
}
@@ -755,13 +840,12 @@ pygowave.view = function () {
var next;
while (offset > 0 && elt != null) {
- if ($type(elt) == "element")
+ if ($type(elt) == "element" && !elt.get('tag') == "iframe")
next = elt.firstChild; // descend
else
next = null;
if (next != null) {
- offset--;
elt = next;
continue;
}
@@ -771,15 +855,16 @@ pygowave.view = function () {
return [elt, offset];
offset -= elt.textContent.length;
}
- else
+ else if (elt.get('tag') == "br" || elt.get('tag') == "iframe")
offset--;
next = elt.nextSibling;
if (next == null) { // ascend
elt = elt.parentNode;
- offset--;
elt = elt.nextSibling;
}
+ else
+ elt = next;
}
return [elt, offset];
@@ -804,7 +889,8 @@ pygowave.view = function () {
* 'after', or 'before'.
*/
initialize: function (view, blip, parentElement, where) {
- var contentElement = new Element('textarea', {'class': 'blip_widget'});
+ var text = blip.content().replace("\n", "<br />");
+ var contentElement = new Element('textarea', {'class': 'blip_widget', 'text': text});
this.parent(parentElement, contentElement, where);
this._medit = new BlipEditor(view, blip, contentElement, {'paragraphise': false, 'toolbar': false});
}
@@ -934,7 +1020,6 @@ pygowave.view = function () {
var WaveView = new Class({
Implements: [Options, Events],
options: {
- participantSearchUrl: "about:blank",
gadgetLoaderUrl: "about:blank",
defaultThumbnailUrl: "about:blank",
rtl: false
@@ -946,6 +1031,7 @@ pygowave.view = function () {
* <br/>Note: This event is fired by a AddParticipantWindow instance.
*
* @event onSearchForParticipant
+ * @param {String} waveletId ID of the Wavelet
* @param {String} text Entered search query
*/
@@ -978,8 +1064,6 @@ pygowave.view = function () {
* @param {pygowave.model.WaveModel} model Model to connect to
* @param {Element} container Container DOM element to render the view on
* @param {Object} options Various settings to correctly render the view
- * @... {String} participantSearchUrl URL to the participant serach view;
- * prepared to have the query appended *subject to change*
* @... {String} gadgetLoaderUrl URL to the gadget loader view; prepared
* to have the gadget URL appended
* @... {String} defaultThumbnailUrl URL to a thumbnail image to be used
@@ -1026,6 +1110,34 @@ pygowave.view = function () {
*/
defaultThumbnailUrl: function () {
return this.options.defaultThumbnailUrl;
+ },
+
+ /**
+ * Show an error message from the controller
+ *
+ * @function {public} showControllerError
+ * @param {String} message The errormessage to show
+ */
+ showControllerError: function (message) {
+ var buttons = {};
+ buttons[gettext("Oh well")] = function () {MochaUI.closeWindow(this);};
+ new MochaUI.Window({
+ title: gettext("Shit happens..."),
+ type: "modal",
+ content: message,
+ width: 280,
+ height: 140,
+ headerStartColor: [95, 163, 237],
+ headerStopColor: [85, 144, 210],
+ bodyBgColor: [201, 226, 252],
+ closeBgColor: [66, 114, 166],
+ closeColor: [255, 255, 255],
+ cornerRadius: 4,
+ resizable: false,
+ footerHeight: 34,
+ rtl: this.options.rtl,
+ buttons: buttons
+ });
}
});
View
3  pygowave_server/admin.py
@@ -17,7 +17,7 @@
#
from pygowave_server.models import Wave, Wavelet, Blip, GadgetElement, Gadget
-from pygowave_server.models import Participant, ParticipantConn, Element
+from pygowave_server.models import Participant, ParticipantConn, Element, Delta
from django.contrib import admin
admin.site.register(Participant)
@@ -29,3 +29,4 @@
admin.site.register(Blip)
admin.site.register(Element)
admin.site.register(GadgetElement)
+admin.site.register(Delta)
View
46 pygowave_server/common/model.py
@@ -127,6 +127,20 @@ def setOnline(self, online):
self.options["isOnline"] = online
self.fireEvent('onlineStateChanged', online)
+ def updateData(self, obj):
+ """
+ Updates participant data from a JSON-serialized map/dict.<br/>
+ Fires onDataChanged.
+
+ @function {public} updateData
+ @param {Object} obj JSON-serialized participant data
+ """
+ self.options["displayName"] = obj["displayName"]
+ self.options["thumbnailUrl"] = obj["thumbnailUrl"]
+ self.options["profileUrl"] = obj["profileUrl"]
+ self.options["isBot"] = obj["isBot"]
+ self.fireEvent('dataChanged')
+
def toGadgetFormat(self):
"""
Convenience function to serialize a participant object into the
@@ -420,7 +434,7 @@ class Blip(object):
"""
# ---------------------------
- def __init__(self, wavelet, id, options, parent = None):
+ def __init__(self, wavelet, id, options, content = "", parent = None):
"""
Called on instantiation. Documented for internal purposes.
@constructor {private} initialize
@@ -434,6 +448,7 @@ def __init__(self, wavelet, id, options, parent = None):
@... {Date} last_modified Date of last modification
@... {int} version Version of the Blip
@... {Boolean} submitted True if this Blip is submitted
+ @param {options String} content Content of the Blip
@param {optional Blip} parent Parent Blip if this is a nested Blip
"""
self.setOptions(options)
@@ -441,7 +456,7 @@ def __init__(self, wavelet, id, options, parent = None):
self._id = id
self._parent = parent
- self._content = ""
+ self._content = content
self._elements = []
self._annotations = []
@@ -519,6 +534,13 @@ def deleteText(self, index, length, noevent = False):
if not noevent:
self.fireEvent("deleteText", [index, length])
+
+ def content(self):
+ """
+ Returns the text content of this Blip.
+ @function {public String} content
+ """
+ return self._content
@Implements(Options, Events)
@Class
@@ -595,6 +617,13 @@ def id(self):
"""
return self._id
+ def waveId(self):
+ """
+ Returns the ID of this Wavelet's Wave.
+ @function {public String} waveId
+ """
+ return self._wave.id()
+
def addParticipant(self, participant):
"""
Add a participant to this Wavelet.<br/>
@@ -640,7 +669,7 @@ def allParticipantsForGadget(self):
ret[id] = participant.toGadgetFormat()
return ret
- def appendBlip(self, id, options):
+ def appendBlip(self, id, options, content = ""):
"""
Convenience function for inserting a new Blip at the end.
For options see the {@link pygowave.model.Blip.initialize Blip constructor}.<br/>
@@ -649,10 +678,11 @@ def appendBlip(self, id, options):
@function {public Blip} appendBlip
@param {String} id ID of the new Blip
@param {Object} options Information about the Blip
+ @param {options String} content Content of the Blip
"""
- return self.insertBlip(len(self._blips), id, options)
+ return self.insertBlip(len(self._blips), id, options, content)
- def insertBlip(self, index, id, options):
+ def insertBlip(self, index, id, options, content = ""):
"""
Insert a new Blip at the specified index.
For options see the {@link pygowave.model.Blip.initialize Blip constructor}.<br/>
@@ -662,8 +692,9 @@ def insertBlip(self, index, id, options):
@param {int} index Index where to insert the Blip
@param {String} id ID of the new Blip
@param {Object} options Information about the Blip
+ @param {options String} content Content of the Blip
"""
- blip = Blip(self, id, options)
+ blip = Blip(self, id, options, content)
self._blips.insert(index, blip)
self.fireEvent('blipInserted', [index, id])
return blip
@@ -775,8 +806,7 @@ def loadFromSnapshot(self, obj, participants):
"version": blip["version"],
"submitted": blip["submitted"]
}
- blipObj = rootWaveletObj.appendBlip(blip_id, blip_options)
- blipObj.insertText(0, blip["content"])
+ blipObj = rootWaveletObj.appendBlip(blip_id, blip_options, blip["content"])
def createWavelet(self, id, options):
"""
View
19 pygowave_server/common/operations.py
@@ -93,14 +93,15 @@ def __init__(self, op_type, wave_id, wavelet_id, blip_id='', index=-1, prop=None
@constructor {public} initialize
@param {String} op_type Type of operation
@param {String} wave_id The id of the wave that this operation is to
- be applied.
+ be applied.
@param {String} wavelet_id The id of the wavelet that this operation is
- to be applied.
- @param {String} blip_id The optional id of the blip that this operation
- is to be applied.
- @param {int} index Optional integer index for content-based operations.
- @param {Object} prop A weakly typed property object is based on the
- context of this operation.
+ to be applied.
+ @param {optional String} blip_id The optional id of the blip that this
+ operation is to be applied.
+ @param {optional int} index Optional integer index for content-based
+ operations.
+ @param {optional Object} prop A weakly typed property object is based
+ on the context of this operation.
"""
self.type = op_type
self.wave_id = wave_id
@@ -434,13 +435,13 @@ def put(self, ops):
self.operations.extend(ops)
self.fireEvent("afterOperationsInserted", [start, end])
- def serialize(self, fetch):
+ def serialize(self, fetch = False):
"""
Serialize this manager's operations into a list of dictionaries.
Set fetch to true to also clear this manager.
@function {public Object[]} serialize
- @param {Boolean} fetch
+ @param {optional Boolean} fetch
"""
if fetch:
ops = self.fetch()
View
57 pygowave_server/models.py
@@ -27,6 +27,7 @@
from django.utils import simplejson
from pygowave_server.utils import find_random_id, gen_random_id, datetime2milliseconds
+from pygowave_server.common.operations import OpManager, DOCUMENT_DELETE, DOCUMENT_INSERT
__author__ = "patrick.p2k.schneider@gmail.com"
@@ -237,6 +238,21 @@ def serialize_blips(self):
for blip in self.blips.all():
blipmap[blip.id] = blip.serialize()
return blipmap
+
+ def applyOperations(self, ops):
+ """
+ Apply the operations on the wavelet.
+
+ """
+
+ for op in ops:
+ if op.blip_id != "":
+ blip = self.blips.get(pk=op.blip_id)
+ if op.type == DOCUMENT_DELETE:
+ blip.text = blip.text[:op.index] + blip.text[op.index+op.property:]
+ elif op.type == DOCUMENT_INSERT:
+ blip.text = blip.text[:op.index] + op.property + blip.text[op.index:]
+ blip.save()
class DataDocument(models.Model):
"""
@@ -262,15 +278,15 @@ class Blip(models.Model):
id = models.CharField(max_length=42, primary_key=True)
wavelet = models.ForeignKey(Wavelet, related_name="blips")
- parent = models.ForeignKey("self", related_name="children", null=True)
+ parent = models.ForeignKey("self", related_name="children", null=True, blank=True)
creator = models.ForeignKey(Participant, related_name="created_blips")
version = models.IntegerField(default=0)
last_modified = models.DateTimeField(auto_now=True)
submitted = models.BooleanField()
- contributors = models.ManyToManyField(Participant, related_name="contributed_blips")
+ contributors = models.ManyToManyField(Participant, related_name="contributed_blips", blank=True)
- text = models.TextField()
+ text = models.TextField(blank=True)
@transaction.commit_on_success
def insertText(self, index, text):
@@ -427,7 +443,7 @@ class InlineBlip(Element):
"""
- blip = models.ForeignKey(Blip)
+ i_blip = models.ForeignKey(Blip)
class GadgetElement(Element):
"""
@@ -554,9 +570,40 @@ class Delta(models.Model):
timestamp = models.DateTimeField(auto_now_add=True)
version = models.IntegerField()
- wavelet = models.ForeignKey(Wavelet, related_name="diffs")
+ wavelet = models.ForeignKey(Wavelet, related_name="deltas")
operations = models.TextField() # JSON again
+
+ @classmethod
+ def createByOpManager(cls, opman, version):
+ """
+ Set this delta's values based on the given OpManager and a version.
+
+ """
+ newobj = cls(
+ version=version,
+ wavelet= Wavelet.objects.get(pk=opman.wavelet_id),
+ operations=simplejson.dumps(opman.serialize())
+ )
+ newobj._OpManager = opman
+
+ return newobj
+
+ def getOpManager(self):
+ """
+ Retrieve an OpManager based on this delta.
+
+ """
+ opman = getattr(self, "_OpManager", None)
+ if opman == None:
+ opman = OpManager(self.wavelet.wave.id, self.wavelet.id)
+ opman.unserialize(simplejson.loads(self.operations))
+ self._OpManager = opman
+
+ return opman
+
+ def __unicode__(self):
+ return u"Delta #%d v%d@%s" % (self.id, self.version, self.wavelet.id)
class Gadget(models.Model):
"""
View
1  pygowave_server/urls.py
@@ -28,7 +28,6 @@
(r'^settings/$', settings),
(r'^waves/$', wave_list),
(r'^waves/(?P<wave_id>\w+)/$', wave),
- (r'^participants/search$', search_participants),
(r'^gadgets/$', all_gadgets),
(r'^gadgets/mine/$', my_gadgets),
(r'^gadgets/load/$', gadget_loader),
View
28 pygowave_server/views.py
@@ -36,8 +36,6 @@
import urllib2, os
from lxml.etree import XMLSyntaxError
-from pycow import translate_file, ParseError
-
def index(request):
auth_fail = False
@@ -219,31 +217,6 @@ def wave(request, wave_id):
"participant_id": participant.id
}, context_instance=RequestContext(request))
-@login_required
-def search_participants(request):
-
- if request.GET.has_key("q"):
- q = request.GET["q"]
- else:
- q = ""
-
- if len(q) < 3: # TODO - Hardcoded
- return render_to_response('pygowave_server/contacts/search_participants.html', {"too_short": 3}, context_instance=RequestContext(request))
-
- me = request.user.get_profile()
-
- lst = []
- for p in Participant.objects.filter(name__icontains=q):
- if p == me:
- continue
- if p.avatar:
- avatar = p.avatar
- else:
- avatar = django_settings.AVATAR_URL + "default.png"
- lst.append({"id": p.id, "avatar": avatar, "profile": p.profile, "name": p.name})
-
- return render_to_response('pygowave_server/contacts/search_participants.html', {"matches": lst}, context_instance=RequestContext(request))
-
def gadget_loader(request):
"""
Load a gadget from any URL.
@@ -297,6 +270,7 @@ def pycow(request, filename):
srcfile = COMMON_FOLDER + filename[:-2] + "py"
if not os.path.exists(cachefile) or os.path.getmtime(srcfile) > os.path.getmtime(cachefile):
+ from pycow import translate_file, ParseError
try:
translate_file(srcfile, cachefile, namespace=module, warnings=False)
except ParseError, e:
View
0  registration/__init__.py
No changes.
View
11 registration/admin.py
@@ -1,11 +0,0 @@
-from django.contrib import admin
-
-from registration.models import RegistrationProfile
-
-
-class RegistrationAdmin(admin.ModelAdmin):
- list_display = ('__unicode__', 'activation_key_expired')
- search_fields = ('user__username', 'user__first_name')
-
-
-admin.site.register(RegistrationProfile, RegistrationAdmin)
View
134 registration/forms.py
@@ -1,134 +0,0 @@
-"""
-Forms and validation code for user registration.
-
-"""
-
-
-from django.contrib.auth.models import User
-from django import forms
-from django.utils.translation import ugettext_lazy as _
-
-from registration.models import RegistrationProfile
-
-
-# I put this on all required fields, because it's easier to pick up
-# on them with CSS or JavaScript if they have a class of "required"
-# in the HTML. Your mileage may vary. If/when Django ticket #3515
-# lands in trunk, this will no longer be necessary.
-attrs_dict = { 'class': 'required' }
-
-
-class RegistrationForm(forms.Form):
- """
- Form for registering a new user account.
-
- Validates that the requested username is not already in use, and
- requires the password to be entered twice to catch typos.
-
- Subclasses should feel free to add any additional validation they
- need, but should either preserve the base ``save()`` or implement
- a ``save()`` method which returns a ``User``.
-
- """
- username = forms.RegexField(regex=r'^\w+$',
- max_length=30,
- widget=forms.TextInput(attrs=attrs_dict),
- label=_(u'username'))
- email = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict,
- maxlength=75)),
- label=_(u'email address'))
- password1 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict, render_value=False),
- label=_(u'password'))
- password2 = forms.CharField(widget=forms.PasswordInput(attrs=attrs_dict, render_value=False),
- label=_(u'password (again)'))
-
- def clean_username(self):
- """
- Validate that the username is alphanumeric and is not already
- in use.
-
- """
- try:
- user = User.objects.get(username__iexact=self.cleaned_data['username'])
- except User.DoesNotExist:
- return self.cleaned_data['username']
- raise forms.ValidationError(_(u'This username is already taken. Please choose another.'))
-
- def clean(self):
- """
- Verifiy that the values entered into the two password fields
- match. Note that an error here will end up in
- ``non_field_errors()`` because it doesn't apply to a single
- field.
-
- """
- if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
- if self.cleaned_data['password1'] != self.cleaned_data['password2']:
- raise forms.ValidationError(_(u'You must type the same password each time'))
- return self.cleaned_data
-
- def save(self):
- """
- Create the new ``User`` and ``RegistrationProfile``, and
- returns the ``User`` (by calling
- ``RegistrationProfile.objects.create_inactive_user()``).
-
- """
- new_user = RegistrationProfile.objects.create_inactive_user(username=self.cleaned_data['username'],
- password=self.cleaned_data['password1'],
- email=self.cleaned_data['email'])
- return new_user
-
-
-class RegistrationFormTermsOfService(RegistrationForm):
- """
- Subclass of ``RegistrationForm`` which adds a required checkbox
- for agreeing to a site's Terms of Service.
-
- """
- tos = forms.BooleanField(widget=forms.CheckboxInput(attrs=attrs_dict),
- label=_(u'I have read and agree to the Terms of Service'),
- error_messages={ 'required': u"You must agree to the terms to register" })
-
-
-class RegistrationFormUniqueEmail(RegistrationForm):
- """
- Subclass of ``RegistrationForm`` which enforces uniqueness of
- email addresses.
-
- """
- def clean_email(self):
- """
- Validate that the supplied email address is unique for the
- site.
-
- """
- if User.objects.filter(email__iexact=self.cleaned_data['email']):
- raise forms.ValidationError(_(u'This email address is already in use. Please supply a different email address.'))
- return self.cleaned_data['email']
-
-
-class RegistrationFormNoFreeEmail(RegistrationForm):
- """
- Subclass of ``RegistrationForm`` which disallows registration with
- email addresses from popular free webmail services; moderately
- useful for preventing automated spam registrations.
-
- To change the list of banned domains, subclass this form and
- override the attribute ``bad_domains``.
-
- """
- bad_domains = ['aim.com', 'aol.com', 'email.com', 'gmail.com',
- 'googlemail.com', 'hotmail.com', 'hushmail.com',
- 'msn.com', 'mail.ru', 'mailinator.com', 'live.com']
-
- def clean_email(self):
- """
- Check the supplied email address against a list of known free
- webmail domains.
-
- """
- email_domain = self.cleaned_data['email'].split('@')[1]
- if email_domain in self.bad_domains:
- raise forms.ValidationError(_(u'Registration using free email addresses is prohibited. Please supply a different email address.'))
- return self.cleaned_data['email']
View
BIN  registration/locale/ar/LC_MESSAGES/django.mo
Binary file not shown
View
81 registration/locale/ar/LC_MESSAGES/django.po
@@ -1,81 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-09-19 19:30-0500\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-#: forms.py:38
-msgid "username"
-msgstr "اسم المستخدم"
-
-#: forms.py:41
-msgid "email address"
-msgstr "عنوان البريد الالكتروني"
-
-#: forms.py:43
-msgid "password"
-msgstr "كلمة المرور"
-
-#: forms.py:45
-msgid "password (again)"
-msgstr "تأكيد كلمة المرور"
-
-#: forms.py:54
-msgid "Usernames can only contain letters, numbers and underscores"
-msgstr "يمكن أن يحتوي اسم المستخدم على احرف، ارقام وشرطات سطرية فقط"
-
-#: forms.py:59
-msgid "This username is already taken. Please choose another."
-msgstr "اسم المستخدم مسجل مسبقا. يرجى اختيار اسم اخر."
-
-#: forms.py:68
-msgid "You must type the same password each time"
-msgstr "يجب ادخال كلمة المرور مطابقة كل مرة"
-
-#: forms.py:96
-msgid "I have read and agree to the Terms of Service"
-msgstr "أقر بقراءة والموافقة على شروط الخدمة"
-
-#: forms.py:105
-msgid "You must agree to the terms to register"
-msgstr "يجب الموافقة على الشروط للتسجيل"
-
-#: forms.py:124
-msgid ""
-"This email address is already in use. Please supply a different email "
-"address."
-msgstr "عنوان البريد الالكتروني مسجل مسبقا. يرجى تزويد عنوان بريد الكتروني مختلف."
-
-#: forms.py:149
-msgid ""
-"Registration using free email addresses is prohibited. Please supply a "
-"different email address."
-msgstr "يمنع التسجيل باستخدام عناوين بريد الكترونية مجانية. يرجى تزويد عنوان بريد الكتروني مختلف."
-
-#: models.py:188
-msgid "user"
-msgstr "مستخدم"
-
-#: models.py:189
-msgid "activation key"
-msgstr "رمز التفعيل"
-
-#: models.py:194
-msgid "registration profile"
-msgstr "ملف التسجيل الشخصي"
-
-#: models.py:195
-msgid "registration profiles"
-msgstr "ملفات التسجيل الشخصية"
View
BIN  registration/locale/bg/LC_MESSAGES/django.mo
Binary file not shown
View
78 registration/locale/bg/LC_MESSAGES/django.po
@@ -1,78 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
-#
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-09-19 19:30-0500\n"
-"PO-Revision-Date: 2008-03-05 12:37+0200\n"
-"Last-Translator: Vladislav <vladislav.mitov@gmail.com>\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-"X-Poedit-Bookmarks: -1,-1,-1,-1,10,-1,-1,-1,-1,-1\n"
-
-#: forms.py:38
-msgid "username"
-msgstr "Потребителско име "
-
-#: forms.py:41
-msgid "email address"
-msgstr "Електронна поща"
-
-#: forms.py:43
-msgid "password"
-msgstr "Парола"
-
-#: forms.py:45
-msgid "password (again)"
-msgstr "Парола (проверка)"
-
-#: forms.py:54
-msgid "Usernames can only contain letters, numbers and underscores"
-msgstr "Потребителските имена могат да съдържат букви, цифри и подчертавки"
-
-#: forms.py:59
-msgid "This username is already taken. Please choose another."
-msgstr "Потребителското име е заето. Моля изберето друго."
-
-#: forms.py:68
-msgid "You must type the same password each time"
-msgstr "Грешка при проверка на паролата."
-
-#: forms.py:96
-msgid "I have read and agree to the Terms of Service"
-msgstr "Прочел съм и съм съгласен с условията за експлоатация"
-
-#: forms.py:105
-msgid "You must agree to the terms to register"
-msgstr "Трябва да сте съгласни с условията за да се регистрирате."
-
-#: forms.py:124
-msgid "This email address is already in use. Please supply a different email address."
-msgstr "Адреса на електронната поща е използван. Моля въведете друг адрес."
-
-#: forms.py:149
-msgid "Registration using free email addresses is prohibited. Please supply a different email address."
-msgstr "Регистрациите с безплатни адреси е забранен. Моля въведете различен адрес за електронна поща"
-
-#: models.py:188
-msgid "user"
-msgstr "Потребител"
-
-#: models.py:189
-msgid "activation key"
-msgstr "Ключ за активация"
-
-#: models.py:194
-msgid "registration profile"
-msgstr "регистрационен профил"
-
-#: models.py:195
-msgid "registration profiles"
-msgstr "регистрационни профили"
-
View
BIN  registration/locale/de/LC_MESSAGES/django.mo
Binary file not shown
View
85 registration/locale/de/LC_MESSAGES/django.po
@@ -1,85 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# Jannis Leidel <jannis@leidel.info>, 2007.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: django-registration 0.3 \n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-09-19 19:30-0500\n"
-"PO-Revision-Date: 2007-09-29 16:50+0200\n"
-"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
-"Language-Team: Deutsch <de@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-#: forms.py:38
-msgid "username"
-msgstr "Benutzername"
-
-#: forms.py:41
-msgid "email address"
-msgstr "E-Mail-Adresse"
-
-#: forms.py:43
-msgid "password"
-msgstr "Passwort"
-
-#: forms.py:45
-msgid "password (again)"
-msgstr "Passwort (wiederholen)"
-
-#: forms.py:54
-msgid "Usernames can only contain letters, numbers and underscores"
-msgstr "Benutzernamen können nur Buchstaben, Zahlen und Unterstriche enthalten"
-
-#: forms.py:59
-msgid "This username is already taken. Please choose another."
-msgstr "Dieser Benutzername ist schon vergeben. Bitte einen anderen wählen."
-
-#: forms.py:68
-msgid "You must type the same password each time"
-msgstr "Bitte das gleiche Passwort zur Überprüfung nochmal eingeben"
-
-#: forms.py:96
-msgid "I have read and agree to the Terms of Service"
-msgstr "Ich habe die Nutzungsvereinbarung gelesen und stimme ihr zu"
-
-#: forms.py:105
-msgid "You must agree to the terms to register"
-msgstr "Sie müssen der Nutzungsvereinbarung zustimmen, um sich zu registrieren"
-
-#: forms.py:124
-msgid ""
-"This email address is already in use. Please supply a different email "
-"address."
-msgstr ""
-"Diese E-Mail-Adresse wird schon genutzt. Bitte geben Sie eine andere "
-"E-Mail-Adresse an."
-
-#: forms.py:149
-msgid ""
-"Registration using free email addresses is prohibited. Please supply a "
-"different email address."
-msgstr ""
-"Die Registrierung mit einer kostenlosen E-Mail-Adresse ist untersagt. Bitte "
-"geben Sie eine andere E-Mail-Adresse an."
-
-#: models.py:188
-msgid "user"
-msgstr "Benutzer"
-
-#: models.py:189
-msgid "activation key"
-msgstr "Aktivierungsschlüssel"
-
-#: models.py:194
-msgid "registration profile"
-msgstr "Registrierungsprofil"
-
-#: models.py:195
-msgid "registration profiles"
-msgstr "Registrierungsprofile"
View
BIN  registration/locale/el/LC_MESSAGES/django.mo
Binary file not shown
View
84 registration/locale/el/LC_MESSAGES/django.po
@@ -1,84 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# Panos Laganakos <panos.laganakos@gmail.com>, 2007.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-09-19 19:30-0500\n"
-"PO-Revision-Date: 2007-11-14 21:50+0200\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-#: forms.py:38
-msgid "username"
-msgstr "όνομα χρήστη"
-
-#: forms.py:41
-msgid "email address"
-msgstr "διεύθυνση ηλεκτρονικού ταχυδρομείου"
-
-#: forms.py:43
-msgid "password"
-msgstr "συνθηματικό"
-
-#: forms.py:45
-msgid "password (again)"
-msgstr "συνθηματικό (ξανά)"
-
-#: forms.py:54
-msgid "Usernames can only contain letters, numbers and underscores"
-msgstr "Τα ονόματα χρηστών μπορούν να περιλαμβάνουν μόνο γράμματα, αριθμούς και υπογραμμίσεις"
-
-#: forms.py:59
-msgid "This username is already taken. Please choose another."
-msgstr "Αυτό το όνομα χρήστη χρησιμοποίειται ήδη. Παρακαλώ διαλέξτε ένα άλλο."
-
-#: forms.py:68
-msgid "You must type the same password each time"
-msgstr "Πρέπει να εισάγετε το ίδιο συνθηματικό κάθε φορά"
-
-#: forms.py:96
-msgid "I have read and agree to the Terms of Service"
-msgstr "Διάβασα και συμφωνώ με τους Όρους της Υπηρεσίας"
-
-#: forms.py:105
-msgid "You must agree to the terms to register"
-msgstr "Πρέπει να συμφωνείται με τους όρους για να εγγραφείτε"
-
-#: forms.py:124
-msgid ""
-"This email address is already in use. Please supply a different email "
-"address."
-msgstr ""
-"Η συγκεκριμένη διεύθυνση ηλεκτρονικού ταχυδρομείου χρησιμοποιείται ήδη. "
-"Παρακαλώ δώστε κάποια άλλη."
-
-#: forms.py:149
-msgid ""
-"Registration using free email addresses is prohibited. Please supply a "
-"different email address."
-msgstr ""
-"Η εγγραφή μέσω δωρεάν διευθύνσεων ηλεκτρονικού ταχυδρομείου απαγορεύεται. ""Παρακαλώ δώστε κάποια άλλη."
-
-#: models.py:188
-msgid "user"
-msgstr "χρήστης"
-
-#: models.py:189
-msgid "activation key"
-msgstr "κλειδί ενεργοποίησης"
-
-#: models.py:194
-msgid "registration profile"
-msgstr "προφίλ εγγραφής"
-
-#: models.py:195
-msgid "registration profiles"
-msgstr "προφίλ εγγραφών"
View
BIN  registration/locale/en/LC_MESSAGES/django.mo
Binary file not shown
View
81 registration/locale/en/LC_MESSAGES/django.po
@@ -1,81 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-09-19 19:30-0500\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-#: forms.py:38
-msgid "username"
-msgstr ""
-
-#: forms.py:41
-msgid "email address"
-msgstr ""
-
-#: forms.py:43
-msgid "password"
-msgstr ""
-
-#: forms.py:45
-msgid "password (again)"
-msgstr ""
-
-#: forms.py:54
-msgid "Usernames can only contain letters, numbers and underscores"
-msgstr ""
-
-#: forms.py:59
-msgid "This username is already taken. Please choose another."
-msgstr ""
-
-#: forms.py:68
-msgid "You must type the same password each time"
-msgstr ""
-
-#: forms.py:96
-msgid "I have read and agree to the Terms of Service"
-msgstr ""
-
-#: forms.py:105
-msgid "You must agree to the terms to register"
-msgstr ""
-
-#: forms.py:124
-msgid ""
-"This email address is already in use. Please supply a different email "
-"address."
-msgstr ""
-
-#: forms.py:149
-msgid ""
-"Registration using free email addresses is prohibited. Please supply a "
-"different email address."
-msgstr ""
-
-#: models.py:188
-msgid "user"
-msgstr ""
-
-#: models.py:189
-msgid "activation key"
-msgstr ""
-
-#: models.py:194
-msgid "registration profile"
-msgstr ""
-
-#: models.py:195
-msgid "registration profiles"
-msgstr ""
View
BIN  registration/locale/es/LC_MESSAGES/django.mo
Binary file not shown
View
85 registration/locale/es/LC_MESSAGES/django.po
@@ -1,85 +0,0 @@
-# Spanish translation for django-registration.
-# Copyright (C) 2007, James Bennet
-# This file is distributed under the same license as the registration package.
-# Ernesto Rico Schmidt <e.rico.schmidt@gmail.com>, 2008.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: django-registration 0.3 \n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2008-03-11 00:19-0400\n"
-"PO-Revision-Date: 2008-03-11 00:19-0400\n"
-"Last-Translator: Ernesto Rico Schmidt <e.rico.schmidt@gmail.com>\n"
-"Language-Team: Español <de@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-#: forms.py:38
-msgid "username"
-msgstr "nombre de usuario"
-
-#: forms.py:41
-msgid "email address"
-msgstr "dirección de coreo electrónico"
-
-#: forms.py:43
-msgid "password"
-msgstr "contraseña"
-
-#: forms.py:45
-msgid "password (again)"
-msgstr "contraseña (otra vez)"
-
-#: forms.py:54
-msgid "Usernames can only contain letters, numbers and underscores"
-msgstr "Los nombres de usuarios sólo pueden contener letras, números y guiones bajos"
-
-#: forms.py:59
-msgid "This username is already taken. Please choose another."
-msgstr "Este nombre de usuario ya está ocupado. Por favor escoge otro"
-
-#: forms.py:71
-msgid "You must type the same password each time"
-msgstr "Tienes que introducir la misma contraseña cada vez"
-
-#: forms.py:100
-msgid "I have read and agree to the Terms of Service"
-msgstr "He leído y acepto los términos de servicio"
-
-#: forms.py:109
-msgid "You must agree to the terms to register"
-msgstr "Tienes que aceptar los términos para registrarte"
-
-#: forms.py:128
-msgid ""
-"This email address is already in use. Please supply a different email "
-"address."
-msgstr ""
-"La dirección de correo electrónico ya está siendo usada. Por favor"
-"proporciona otra dirección."
-
-#: forms.py:153
-msgid ""
-"Registration using free email addresses is prohibited. Please supply a "
-"different email address."
-msgstr ""
-"El registro usando una dirección de correo electrónico gratis está prohibido."
-"Por favor proporciona otra dirección."
-
-#: models.py:188
-msgid "user"
-msgstr "usuario"
-
-#: models.py:189
-msgid "activation key"
-msgstr "clave de activación"
-
-#: models.py:194
-msgid "registration profile"
-msgstr "perfil de registro"
-
-#: models.py:195
-msgid "registration profiles"
-msgstr "perfiles de registro"
View
BIN  registration/locale/es_AR/LC_MESSAGES/django.mo
Binary file not shown
View
83 registration/locale/es_AR/LC_MESSAGES/django.po
@@ -1,83 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) 2008 Leonardo Manuel Rocha
-# This file is distributed under the same license as the PACKAGE package.
-# FIRST AUTHOR <l e o m a r o at g m a i l dot c o m>, YEAR.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: PACKAGE VERSION\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-09-19 19:30-0500\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-#: forms.py:38
-msgid "username"
-msgstr "nombre de usuario"
-
-#: forms.py:41
-msgid "email address"
-msgstr "dirección de e-mail"
-
-#: forms.py:43
-msgid "password"
-msgstr "contraseña"
-
-#: forms.py:45
-msgid "password (again)"
-msgstr "contraseña (nuevamente)"
-
-#: forms.py:54
-msgid "Usernames can only contain letters, numbers and underscores"
-msgstr "El nombre de usuario solo puede contener letras, números y guiones bajos"
-
-#: forms.py:59
-msgid "This username is already taken. Please choose another."
-msgstr "Ese nombre de usuario ya está asignado. Por favor elija otro."
-
-#: forms.py:68
-msgid "You must type the same password each time"
-msgstr "Debe tipear la misma contraseña cada vez"
-
-#: forms.py:96
-msgid "I have read and agree to the Terms of Service"
-msgstr "He leído y estoy de acuerdo con las Condiciones de Servicio"
-
-#: forms.py:105
-msgid "You must agree to the terms to register"
-msgstr "Debe estar de acuerdo con las Condiciones para poder registrarse"
-
-#: forms.py:124
-msgid ""
-"This email address is already in use. Please supply a different email "
-"address."
-msgstr "Esa dirección de e-mail ya está en uso. Por favor provea otra "
-"dirección."
-
-#: forms.py:149
-msgid ""
-"Registration using free email addresses is prohibited. Please supply a "
-"different email address."
-msgstr "La registración con un e-mail gratuito está prohibida. Por favor "
-"de una dirección de e-mail diferente."
-
-#: models.py:188
-msgid "user"
-msgstr "usuario"
-
-#: models.py:189
-msgid "activation key"
-msgstr "clave de activación"
-
-#: models.py:194
-msgid "registration profile"
-msgstr "perfil de registro"
-
-#: models.py:195
-msgid "registration profiles"
-msgstr "perfiles de registro"
View
BIN  registration/locale/fr/LC_MESSAGES/django.mo
Binary file not shown
View
81 registration/locale/fr/LC_MESSAGES/django.po
@@ -1,81 +0,0 @@
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
-# This file is distributed under the same license as the PACKAGE package.
-# Samuel Adam <samuel.adam@gmail.com>, 2007.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: django-registration 0.3 \n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-09-19 19:30-0500\n"
-"PO-Revision-Date: 2007-09-20 10:30+0100\n"
-"Last-Translator: Samuel Adam <samuel.adam@gmail.com>\n"
-"Language-Team: Français <fr@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-#: forms.py:38
-msgid "username"
-msgstr "pseudo"
-
-#: forms.py:41
-msgid "email address"
-msgstr "adresse email"
-
-#: forms.py:43
-msgid "password"
-msgstr "mot de passe"
-
-#: forms.py:45
-msgid "password (again)"
-msgstr "mot de passe (vérification)"
-
-#: forms.py:54
-msgid "Usernames can only contain letters, numbers and underscores"
-msgstr "Le pseudo ne peut contenir que des lettres, chiffres et le caractère souligné."
-
-#: forms.py:59
-msgid "This username is already taken. Please choose another."
-msgstr "Ce pseudo est déjà utilisé. Veuillez en choisir un autre."
-
-#: forms.py:68
-msgid "You must type the same password each time"
-msgstr "Veuillez indiquer le même mot de passe dans les deux champs"
-
-#: forms.py:96
-msgid "I have read and agree to the Terms of Service"
-msgstr "J'ai lu et accepté les Conditions Générales d'Utilisation"
-
-#: forms.py:105
-msgid "You must agree to the terms to register"
-msgstr "Vous devez accepter les conditions d'utilisation pour vous inscrire"
-
-#: forms.py:124
-msgid ""
-"This email address is already in use. Please supply a different email "
-"address."
-msgstr "Cette adresse email est déjà utilisée. Veuillez en indiquer une autre."
-
-#: forms.py:149
-msgid ""
-"Registration using free email addresses is prohibited. Please supply a "
-"different email address."
-msgstr "L'inscription avec une adresse email d'un compte gratuit est interdite. Veuillez en indiquer une autre."
-